3.2 無狀態設計與 Session 共享:Redis / JWT / 黏性會話#
加了三台機器,購物車卻丟了?#
3.1 節搭好 Nginx 集群,壓測很漂亮。但一上線客訴就炸了:「購物車明明加了,重新整理就不見了!」原因很經典:使用者第一次被分到 Tomcat-1(Session 在它記憶體裡),第二次被分到 Tomcat-2——新機器根本不認識他。
這就是本節的核心矛盾:水平擴展要求機器可隨意替換,但有狀態的 Session 卻把使用者綁死在某一台上。
三條路:黏性會話、集中 Session、JWT#
flowchart TD
U[買家] --> N[Nginx]
N -->|ip_hash黏住| T1[Tomcat-1:Session在本地]
N --> C[方案二:Session外置]
C --> R[(Redis:集中存Session)]
T1 -.-> R
T2[Tomcat-2] -.-> R
T3[Tomcat-3] -.-> R
U -->|方案三:JWT自帶身份| ANY[任一台:驗簽名即可]| 方案 | 做法 | 優點 | 缺點 |
|---|---|---|---|
| 黏性會話(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,但要注意撤銷與密鑰
- 無狀態是彈性擴縮與容器化的前提
想一想#
- 黏性會話在什麼情況下會讓「加機器」完全沒效果?(提示:Hash 固定的舊用戶)
- Redis 集中 Session 後,Redis 本身掛了會怎樣?該如何給 Redis 做高可用?
- JWT 號稱「無狀態」,為什麼登出(強制失效)反而變難了?混合方案如何解決?