單體應用將 Session 存儲在 JVM 堆內存,水平擴展多實例後,用戶請求被負載均衡分發到不同實例,導致「登入狀態丟失、購物車清空」。解決方案:將 Session 外部化至 Redis,所有實例讀寫同一份數據。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
3. 配置 Redis 連線
# application.yml spring: redis: host: localhost port: 6379 password: your_password # 生產環境必須 database: 0 lettuce: pool: max-active: 20 max-idle: 10 min-idle: 5 session: store-type: redis redis: namespace: "spring:session" # 隔離多應用 flush-mode: on_save # 僅屬性變更時寫入 save-mode: on_set_attribute驗證 Session 共享效果
啟動兩個實例(端口 8080、9090),同一瀏覽器訪問:
測試步驟與預期
- 訪問
localhost:8080/demo/put→ 返回{"sessionId":"abc","attr":"Hello from 8080"}- 訪問
localhost:9090/demo/get→ 返回{"sessionId":"abc","attr":"Hello from 8080"}同一 SessionId、數據共享- 訪問
localhost:9090/demo/put→ 更新屬性- 再訪
localhost:8080/demo/get→ 看到最新值 雙向同步原理解析
@EnableRedisHttpSession 導入 SpringHttpSessionConfiguration,註冊 SessionRepositoryFilter;RedisOperationsSessionRepository 讀寫 Redis Hash(Key: spring:session:sessions:{id});SESSION 傳遞 SessionId,默認 30 分鐘滑動過期。| 優化點 | 配置/建議 |
|---|---|
| 序列化 | 改用 GenericJackson2JsonRedisSerializer 替代 JDK 序列化,體積小、可讀、跨語言 |
| 高可用 | Redis Cluster / Sentinel + Lettuce MasterSlaveTopologyRefresh |
| Session 固定 | 關鍵業務(支付、下單)可綁定用戶 ID 作為 Session 附加屬性,防止會話劫持 |
| 監控 | 暴露 spring.session.redis.* 指標接入 Prometheus/Grafana |
| Nginx 親和性 | 若無法全共享,可用 ip_hash 降低跨實例概率(不推薦長期依賴) |