Spring Boot + Redis 處理 Session 共享
〇、背景
Web 開發中,通過 Session 在服務端記錄用戶狀態是很常見的操作。對于 Web 開發中 Session、Cookie 等概念請參考《Session 機制詳解》。但是 Session 的機制對于單機應用是沒問題的,但是對于集群環境,由于在將請求分配到另一臺服務器時,新的服務器無法通過瀏覽器傳入的 Cookie 值取到 Session,所以導致所有基于 Session 的操作都會失敗,如:登錄狀態。
本文通過搭建一個非常簡易的集群環境,來演示 Session 機制在集群環境中存在的問題,并通過 Redis 進行 Session 共享來解決該問題。
一、問題再現
1、測試環境
(1)App Server
使用 Spring Boot 2 寫一個簡單的 Web 應用,提供兩個鏈接:
Controller 部分代碼如下:
@RestController
public class TestController {
@GetMapping("/set-session")
public Object writeSession(String sessionVal, HttpSession httpSession) {
System.out.println("Param 'sessionVal' = " + sessionVal);
httpSession.setAttribute("sessionVal", sessionVal);
return sessionVal;
}
@GetMapping("/get-session")
public Object readSession(HttpSession httpSession) {
Object obj = httpSession.getAttribute("sessionVal");
System.out.println("'sessionVal' in Session = " + obj);
return obj;
}
}
單機測試通過。
(2)通過 Nginx 做負載均衡
分別在 9001 和 9002 兩個端口啟動 App Server,然后通過 Nginx 配置負載均衡,配置如下:
http {
upstream app_server {
server 127.0.0.1:9001;
server 127.0.0.1:9002;
}
server {
listen 9000;
location / {
proxy_pass http://app_server;
}
}
}
測試失敗。
二、原因分析
主要是因為原來 A 服務器將其 Session 的標識 Cookie_for_Session_A 放入瀏覽器 Cookie,當下一次請求被分配到 B 服務器,B 服務器無法通過 Cookie_for_Session_A 獲取到對應的 Session,導致失敗。
解決的思路,主要是引入三方服務器,將 Session 保存到三方服務器,A、B 服務器共享三方服務器中的 Session 數據。
三、解決方案
引入 Redis 作為三方服務器存儲 Session 數據。
1、引入 Redis 相關庫
dependencies {
implementation('org.springframework.boot:spring-boot-starter-web')
implementation('org.springframework.boot:spring-boot-starter-data-redis')
// https://mvnrepository.com/artifact/org.springframework.session/spring-session-data-redis
compile group: 'org.springframework.session', name: 'spring-session-data-redis', version: '2.1.2.RELEASE'
}
2、配置 Redis 連接
application.yml,這里為了演示清晰,只做了最簡配置,正式使用請調整相關參數
spring:
redis:
host: 127.0.0.1
port: 6379
3、開啟配置
創建一個配置類 SessionConfig,類名隨意。
關鍵是兩個注解:
@Configuration
@EnableRedisHttpSession
@Configuration
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 86400*30)
public class SessionConfig {
}
4、打包、運行測試
執行 Gradle 的 bootJar 任務,然后按照前面的方式,分別在 9001 和 9002 端口運行 jar 包:
java -jar redis-session.jar
java -jar redis-session.jar
測試通過。