使用Spring session實現分佈式應用session共享

Session與Cookie基礎

由於http協議是無狀態的協議,爲了能夠記住請求的狀態,於是引入了Session和Cookie的機制。我們應該有一個很明確的概念,那就是Session是存在於服務器端的,在單體式應用中,他是由tomcat管理的,存在於tomcat的內存中,當我們爲了解決分佈式場景中的session共享問題時,引入了redis,其共享內存,以及支持key自動過期的特性,非常契合session的特性,我們在企業開發中最常用的也就是這種模式。但是隻要你願意,也可以選擇存儲在JDBC,Mongo中,這些,spring都提供了默認的實現,在大多數情況下,我們只需要引入配置即可。而Cookie則是存在於客戶端,更方便理解的說法,可以說存在於瀏覽器。

代碼示例

使用Springboot編寫一個非常簡單的服務端,來加深對其的理解。需求很簡單,當瀏覽器訪問localhost:8000/test/cookie?browser=xxx時,如果沒有獲取到session,則將request中的browser存入session;如果獲取到session,便將session中的browser值輸出。順便將request中的所有cookie打印出來。
創建一個hellospring項目。

@Controller
public class CookieController {
    @RequestMapping("/test/cookie")
    public String cookie(@RequestParam("browser") String browser, HttpServletRequest request, HttpSession session) {
        //取出session中的browser
        Object sessionBrowser = session.getAttribute("browser");
        if (sessionBrowser == null) {
            System.out.println("不存在session,設置browser=" + browser);
            session.setAttribute("browser", browser);
        } else {
            System.out.println("存在session,browser=" + sessionBrowser.toString());
        }
        Cookie[] cookies = request.getCookies();
        if (cookies != null && cookies.length > 0) {
            for (Cookie cookie : cookies) {
                System.out.println(cookie.getName() + " : " + cookie.getValue());
            }
        }
        return "index";
    }
}

使用Redis集成Spring Session

引入依賴,Spring Boot的版本採用1.5.6

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session-data-redis</artifactId>
</dependency>

配置類開啓Redis Http Session

@Configuration
@EnableRedisHttpSession
public class HttpSessionConfig {
}

爲了方便演示多節點之間的session共享,我們生成多個配置文件。
application.properties

spring.redis.host=172.17.0.61
spring.redis.port=7200
spring.redis.database=0
spring.application.name=hellospring

application-default.properties

server.port=8001

application-dev1.properties

server.port=8001

application-dev2.properties

server.port=8002

進入cmd模式,在項目執行mvn package。
分別啓動dev1和dev2。

java -jar hellospring-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev1
java -jar hellospring-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev2

安裝NGINX,將請求分發到兩個不同節點,配置文件如下。

        listen      8000;
        server_name  localhost;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;

        location / {
            proxy_pass http://localhost;  
            proxy_set_header Host $host;  
            proxy_set_header X-Real-IP $remote_addr;  
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 
        }
    upstream localhost{  
         #hash $remote_addr consistent;  
         server 127.0.0.1:8001 weight=1;  
         server 127.0.0.1:8002 weight=1;  
    } 

訪問http://localhost:8000/test/cookie?browser=chrome,觀察console輸出內容。
這裏寫圖片描述
可以看到session當中沒有值,browser被保存到session中,查看redis,發現session數據已經被緩存到redis。
這裏寫圖片描述
解析一下這個redis store。
​1 spring:session是默認的Redis HttpSession前綴(redis中,我們常用’:’作爲分割符)。

2 每一個session都會有三個相關的key,第三個key最爲重要,它是一個HASH數據結構,將內存中的session信息序列化到了redis中。如上文的browser,就被記錄爲sessionAttr:browser=chrome,還有一些meta信息,如創建時間,最後訪問時間等。

3 另外兩個key,expirations:1504446540000和sessions:expires:7079…我發現大多數的文章都沒有對其分析,前者是一個SET類型,後者是一個STRING類型,可能會有讀者發出這樣的疑問,redis自身就有過期時間的設置方式TTL,爲什麼要額外添加兩個key來維持session過期的特性呢?這需要對redis有一定深入的瞭解才能想到這層設計。當然這不是本節的重點,簡單提一下:redis清除過期key的行爲是一個異步行爲且是一個低優先級的行爲,用文檔中的原話來說便是,可能會導致session不被清除。於是引入了專門的expiresKey,來專門負責session的清除,包括我們自己在使用redis時也需要關注這一點。在開發層面,我們僅僅需要關注第三個key就行了。

再次訪問http://localhost:8000/test/cookie?browser=chrome
這裏寫圖片描述
現在dev1也能正常訪問到session內容。

代碼位置:
https://github.com/39627020/experience/tree/master/java/springboot/hellospring

發佈了64 篇原創文章 · 獲贊 16 · 訪問量 20萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章