1. 程式人生 > >Spring Session簡介

Spring Session簡介

Spring-Session介紹

  1. Spring-Session使用的場景?

HttpSession是通過Servlet容器進行建立和管理的,在單機環境中。通過Http請求建立的Session資訊是儲存在Web伺服器記憶體中,如Tomcat/Jetty。

假如當用戶通過瀏覽器訪問應用伺服器,session資訊中儲存了使用者的登入資訊,並且session資訊沒有過期失,效那麼使用者就一直處於登入狀態,可以做一些登入狀態的業務操作!

但是現在很多的伺服器都採用分散式叢集的方式進行部署,一個Web應用,可能部署在幾臺不同的伺服器上,通過LVS或者Nginx等進行負載均衡(一般使用Nginx+Tomcat實現負載均衡)。此時來自同一使用者的Http請求將有可能

被分發到不同的web站點中去(如:第一次分配到A站點,第二次可能分配到B站點)。那麼問題就來了,如何保證不同的web站點能夠共享同一份session資料呢?

假如使用者在發起第一次請求時候訪問了A站點,並在A站點的session中儲存了登入資訊,當用戶第二次發起請求,通過負載均衡請求分配到B站點了,那麼此時B站點能否獲取使用者儲存的登入的資訊呢?答案是不能的,因為上面說明,Session是儲存在對應Web伺服器的記憶體的,不能進行共享,此時Spring-session就出現了,來幫我們解決這個session共享的問題!

  1. 如何進行Session共享呢?

簡單點說就是請求http請求經過Filter職責鏈,根據配置資訊過濾器將建立session的權利由tomcat交給了Spring-session中的SessionRepository,通過Spring-session建立會話,並儲存到對應的地方。

實際上實現Session共享的方案很多,其中一種常用的就是使用Tomcat、Jetty等伺服器提供的Session共享功能,將Session的內容統一儲存在一個數據庫(如MySQL)或快取(如Redis,Mongo)中,

而上面說的使用Nginx也可以,使用ip_hash策略。


【Nginx】實現負載均衡的幾種方式


在使用Nginx的ip_hash策略時候,每個請求按訪問ip的hash結果分配,這樣每個訪客固定訪問一個後端伺服器,也可以解決session的問題。

Spring官方介紹

Why Spring Session & HttpSession?

Spring會話提供了與HttpSession的透明整合,允許以應用程式容器(即Tomcat)中性的方式替換HttpSession,但是我們從中得到了什麼好處呢?

  • 叢集會話——Spring會話使支援叢集會話變得微不足道,而不需要繫結到應用程式容器的特定解決方案。
  • 多個瀏覽器會話——Spring會話支援在單個瀏覽器例項中管理多個使用者會話(也就是多個經過驗證的帳戶,類似於谷歌)。
  • RESTful api——Spring會話允許在header中提供會話id以使用RESTful api。

  • Spring Session & WebSockets的完美整合。

專案搭建

整個專案的整體骨架:

基於XML配置方式的Spring Session

本次只講解xml配置方式,javaConfig配置可以參考官方文件:Spring Java Configuration

環境說明

本次專案需要使用者Nginx和Redis,如果沒有配置Nginx的請看這裡: linux上Nginx的安裝教程詳解

沒有配置Redis的請看這裡:linux環境安裝Redis

配置好了上面的環境,後下面開始正式的Spring-session搭建過程了!

建立專案:

1.新增專案依賴

首先新建一個Maven的Web專案,新建好之後在pom檔案中新增下面的依賴:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.spring</groupId>
    <artifactId>learn-spring-session</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>
    <name>First Learn Spring Session</name>

    <properties>
      <jdk.version>1.8</jdk.version>
      <spring.version>4.3.4.RELEASE</spring.version>
      <spring-session.version>1.3.1.RELEASE</spring-session.version>
    </properties>

    <dependencies>
        <!-- https://mvnrepository.com/artifact/javax.servlet/servlet-api  -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.0.1</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session-data-redis</artifactId>
            <version>${spring-session.version}</version>
            <type>pom</type>
        </dependency>
        
       <dependency>
            <groupId>biz.paluch.redis</groupId>
            <artifactId>lettuce</artifactId>
            <version>3.5.0.Final</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>${spring.version}</version>
        </dependency>
    </dependencies>   

</project>

2.web.xml配置

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
	<display-name>springsession</display-name>
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>classpath*:spring/application-session.xml</param-value>
	</context-param>

	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>

	<servlet>
		<servlet-name>SpringDispatcher</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextClass</param-name>
			<param-value>
				org.springframework.web.context.support.AnnotationConfigWebApplicationContext
			</param-value>
		</init-param>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>com.it</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>
	<servlet-mapping>
		<servlet-name>SpringDispatcher</servlet-name>
		<url-pattern>/</url-pattern>
	</servlet-mapping>

	<filter>
		<filter-name>springSessionRepositoryFilter</filter-name>
		<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
	</filter>
	<filter-mapping>
		<filter-name>springSessionRepositoryFilter</filter-name>
		<url-pattern>/*</url-pattern>
		<dispatcher>REQUEST</dispatcher>
		<dispatcher>ERROR</dispatcher>
	</filter-mapping>
	<session-config>
		<session-timeout>30</session-timeout>
	</session-config>
</web-app>

DelegatingFilterProxy將通過springSessionRepositoryFilter的名稱查詢Bean並將其轉換為過濾器。對於呼叫DelegatingFilterProxy的每個請求,也將呼叫springSessionRepositoryFilter。

3.Xml的配置

添加了必要的依賴之後,我們需要建立相應的Spring配置。Spring配置是要建立一個Servlet過濾器,它用Spring Session支援的HttpSession實現來替換容器本身HttpSession實現。這一步也是Spring Session的核心。在resources 下面新建一個xml,名詞為 application-session.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">

    <context:annotation-config/>
    
    <bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration"/>
   
    <bean class="org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory"/>

</beans>

上述程式碼註釋:

LettuceConnectionFactory例項是配置Redis的ConnectionFactory,檢視原始碼可以看到,預設的Redis連結配置為:

因此,如果有自己的Redis配置,請修改,例如下邊的配置:

<bean class="org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory"> 
<property name="hostName" value="192.168.1.149"/> 
<property name="port" value="6379"/> 
<property name="password" value="123456"/>
</bean>

4、springmvc配置檔案

package com.it.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.InternalResourceViewResolver;

@Configuration
@ComponentScan(basePackages="com.it")
@EnableWebMvc
public class MvcConfiguration extends WebMvcConfigurerAdapter{

	@Bean
	public ViewResolver getViewResolver(){
		InternalResourceViewResolver resolver = new InternalResourceViewResolver();
		resolver.setPrefix("/WEB-INF/views/");
		resolver.setSuffix(".jsp");
		return resolver;
	}
	
	@Override
	public void addResourceHandlers(ResourceHandlerRegistry registry) {
		registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
	}

	
}

5.測試程式碼

新建 SpringSessionController.java

@Controller
@RequestMapping(value = "/spring/session")
public class SpringSessionController {

    @RequestMapping(value = "/")
    public ModelAndView test(HttpServletResponse response) throws IOException {
        return new ModelAndView("home");
    }

    @RequestMapping(value = "/setSession.do", method = RequestMethod.GET)
    public void setSession(HttpServletRequest request, HttpServletResponse response) {
        String name = request.getParameter("name");
        String value = request.getParameter("value");
        request.getSession().setAttribute(name, value);
    }

    @RequestMapping(value = "/getSession.do", method = RequestMethod.GET)
    public void getInterestPro(HttpServletRequest request, HttpServletResponse response) {
        String name = request.getParameter("name");
        System.out.println("------" + request.getSession().getAttribute(name));
    }

    @RequestMapping(value = "/removeSession.do", method = RequestMethod.GET)
    public void removeSession(HttpServletRequest request, HttpServletResponse response) {
        String name = request.getParameter("name");
        request.getSession().removeAttribute(name);
    }

}

 

效果演示

1.啟動Redis,預設埠6379就行!

2.配置Nginx,啟動Nginx

Nginx的配置,權重方式(或輪詢):

#user  nobody;
worker_processes  1;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile        on;
    
    keepalive_timeout  65;

    #gzip  on;
     upstream local_tomcat {
     server localhost:8080 weight=1;
     server localhost:8090 weight=1;  
    }
    server {
        listen       80;
        server_name  localhost:8080;

        location / {
           proxy_pass http://local_tomcat;
          
        }

   

3.啟動Tomcat1和Tomcat2

將上面搭建好的專案放入兩個Tomcat中,分別啟動。

http://192.168.0.147:8090/springsession/spring/session/getSession

http://192.168.0.147:8080/springsession/spring/session/getSession

保證兩個tomcat能正常訪問

 

4、使用Nginx負載均衡均驗證Session是否共享成功,

直接訪問Nginx路徑:http://192.168.0.147/springsession/spring/session/getSession,

 

重新整理頁面,變成8090伺服器,值可以取到,sessionid值不變

如何在Redis中檢視Session資料,可以使用命令,或者在Windows的RedisDesktopManager中檢視!

key的簡單介紹說明:


# 儲存 Session 資料,資料型別hash
Key:spring:session:sessions:XXXXXXX

# Redis TTL觸發Session 過期。(Redis 本身功能),資料型別:String
Key:spring:session:sessions:expires:XXXXX

#執行 TTL key ,檢視剩餘生存時間


#定時Job程式觸發Session 過期。(spring-session 功能),資料型別:Set
Key:spring:session:expirations:XXXXX

驗證成功!