跳轉到

3.2 無狀態設計與 Session 共享:Redis / JWT / 黏性會話#

加了三台機器,購物車卻丟了?#

3.1 節搭好 Nginx 集群,壓測很漂亮。但一上線客訴就炸了:「購物車明明加了,重新整理就不見了!」原因很經典:使用者第一次被分到 Tomcat-1(Session 在它記憶體裡),第二次被分到 Tomcat-2——新機器根本不認識他。

這就是本節的核心矛盾:水平擴展要求機器可隨意替換,但有狀態的 Session 卻把使用者綁死在某一台上。

三條路:黏性會話、集中 Session、JWT#

方案 做法 優點 缺點
黏性會話(Sticky) ip_hash 把同用戶固定打到同台 改動最小,當天就能上 單點脆弱、負載不均、擴縮容掉線
集中 Session(Redis) Session 統一存 Redis,各 Tomcat 共享 真正無狀態、故障可漂移 多一次網路存取、Redis 成關鍵依賴
Token / JWT 伺服器簽發 Token,客戶端每次自帶 伺服器零儲存、跨機房友好 撤銷困難、Token 變大、密鑰管理要做好

黏性會話:最甜也最毒的捷徑#

黏性會話(ip_hash 或 Cookie 路由)上線最快,但三個坑很快會咬你:第一,機器一掛,上面所有用戶的登入態全丟;第二,某個大賣家直播間的粉絲全被 Hash 到同一台,熱點打爆單機;第三,雙十一要加機器時,舊用戶黏在舊機器上,新機器吃不飽。淘寶早期用過,很快就被迫遷走。

Redis 集中 Session:無狀態的標準答案#

做法很固定:Tomcat 開啟 Redis Session Manager(或 Spring Session),每次請求從 Redis 讀寫 Session,Tomcat 本地不留狀態。這樣任一台掛掉,使用者無感知地被 Nginx 切到別台。

# Spring Session + Redis:關鍵配置範例
spring.session.store-type=redis
spring.redis.host=10.0.2.21
spring.redis.port=6379
server.servlet.session.timeout=30m
spring.session.redis.namespace=taobao:session
// 程式碼零改動:照常用 HttpSession,底層已換成 Redis
@GetMapping("/cart/add")
public String addCart(HttpSession session, String sku) {
    List<String> cart = (List<String>) session.getAttribute("cart");
    cart.add(sku);  // 實際讀寫的是 Redis,不是本機記憶體
    return "ok";
}

注意三件事:Session 要足夠小(別塞整件商品詳情);Redis 自身要做主從 + 哨兵(否則它掛了等於全站掉線);敏感操作加二次校驗(只靠 Session ID 不夠)。

JWT:把狀態還給客戶端#

JWT 把用戶身份做成伺服器簽名的字串,瀏覽器每次帶著它來,任何一台機器驗簽名就能認人,伺服器端什麼都不用存。適合 App API 與跨域場景。

# JWT 結構:Header.Payload.Signature(三段 Base64,以 . 分隔)
eyJhbGciOiJIUzI1NiJ9.eyJ1aWQiOiIxMjM0NSIsImV4cCI6MTc4MDAwMDAwMH0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

但 JWT 不是銀彈:簽發後難以提前撤銷(要做黑名單就又變有狀態了),Payload 太大會每個請求都增重,密鑰洩漏等於全站偽造通行證。實務常採混合:短命 Access Token(JWT)+ 長命 Refresh Token(Redis 可撤銷)。

無狀態設計的真正含義#

無狀態不是「沒有狀態」,而是「狀態不放在應用伺服器本地」——放到 Redis、資料庫或客戶端手裡。這樣應用層才能像積木一樣隨意加減、隨意重啟,這正是後面容器化(第七章)的前提:K8s 隨時殺掉重建 Pod,有狀態的機器是沒法這樣玩的。

本章小結#

  • 集群 + 本地 Session = 購物車丟失,根源是狀態綁死機器
  • 黏性會話最快但脆弱:單點丟失、熱點不均、擴容無感
  • Redis 集中 Session是標準答案:Tomcat 無狀態,故障可漂移
  • JWT把狀態還給客戶端,適合 API,但要注意撤銷與密鑰
  • 無狀態是彈性擴縮與容器化的前提

想一想#

  1. 黏性會話在什麼情況下會讓「加機器」完全沒效果?(提示:Hash 固定的舊用戶)
  2. Redis 集中 Session 後,Redis 本身掛了會怎樣?該如何給 Redis 做高可用?
  3. JWT 號稱「無狀態」,為什麼登出(強制失效)反而變難了?混合方案如何解決?