概述#
在討論並行性時,我們提到了業務交易與系統交易之間的差異。這個差異不僅影響並行處理,也影響如何儲存在業務交易中使用、但尚未準備提交到正式資料庫的資料。
業務交易與系統交易的差異,正是無狀態 vs. 有狀態伺服器爭論的核心。作者認為這個問題常常被隱藏在技術層面的討論之後,而基本議題其實是:認清某些 session 本質上就是有狀態的,然後決定如何處理這個狀態。
The Value of Statelessness#
什麼是無狀態伺服器?#
所謂無狀態伺服器,是指一個不在請求之間保留狀態的物件。這樣的物件可能有欄位,但當你呼叫它的方法時,欄位值是未定義的。例如一個回傳書籍資訊的 Web 頁面物件,每次請求時從資料庫取得資料並產生 HTML,處理完後欄位值就無用了。
無狀態的好處#
無狀態的主要好處是伺服器資源的效率。如果物件不保留狀態,就不需要為每個使用者維護一個專屬物件——可以將物件池化,用更少的物件服務更多使用者。
考慮一個場景:100 位使用者,每人每 10 秒發出一個請求,處理需 1 秒。有狀態伺服器需要 100 個物件(90% 時間閒置)。無狀態伺服器只需 10 個物件就能讓所有人忙碌。閒置使用者越多,無狀態伺服器越有價值。
無狀態的限制#
問題是:許多客戶端互動本質上就是有狀態的。 以購物車為例——使用者瀏覽多本書並選擇購買,購物車內容必須在整個 session 中被記住。如果使用者只看書不買,session 是無狀態的;一旦開始購買,session 就變成有狀態的。
我們無法避免狀態的存在(除非永遠不消費);真正的問題是如何用無狀態伺服器來實作有狀態的 session。好消息是這確實可以做到。
Session State#
Session State 的定義#
購物車的內容就是 session state——只與該特定 session 相關的資料。Session state 存在於業務交易之中,與其他 session 及其業務交易是隔離的。
Session state 與 record data 不同。Record data 是長期持久化、存在資料庫中、對所有 session 可見的資料。Session state 需要在業務交易提交時,才轉變為 record data。
Session state 具有類似交易的 ACID 特性。一致性方面,session state 在編輯過程中可能暫時處於無效狀態,只在業務交易提交時才需滿足驗證規則。隔離方面,需要注意在 session 期間,其他人可能已修改了你正在參考的資料,導致不一致讀取。
並非 session 持有的所有資料都算 session state。Session 可能快取一些不需要跨請求儲存但能改善效能的資料。快取可以丟失而不影響正確行為,這與 session state 不同——session state 必須在請求間被保存以確保正確行為。
儲存 Session State 的三種方式#
有三種基本但界線模糊的選擇:
Client Session State#
將資料儲存在客戶端。實現方式包括:在 URL 中編碼資料、使用 cookies、將資料序列化到 Web 表單的隱藏欄位中,或在 rich client 中保存在物件裡。
Server Session State#
將資料保存在伺服器的記憶體中。通常會有機制將 session state 以序列化物件的形式持久化到更耐久的儲存,可以是應用伺服器的本地檔案系統,也可以放在共享資料來源中(如以 session ID 為 key、序列化物件為 value 的資料庫表)。
Database Session State#
同樣是伺服器端儲存,但將資料拆解為表格和欄位,儲存在資料庫中,如同儲存一般的持久資料。
選擇考量#
選擇儲存方式時需考慮多個面向:
頻寬:Client Session State 要求每次請求都傳輸 session 資料。少量欄位問題不大,但大量資料會導致傳輸負擔。即使某些資料不需要在客戶端顯示,也必須全部傳輸。此外還需擔心安全性與完整性——除非加密,否則惡意使用者可能修改 session 資料。
隔離性:Session 資料必須彼此隔離。使用 Database Session State 時,需要特別努力將 session 資料與資料庫中的 record data 隔離開來。
叢集與 Session Migration:如果需要叢集來提升吞吐量,需考慮 session migration(允許 session 在伺服器間移動)vs. server affinity(強制同一 session 的所有請求由同一伺服器處理)。Server Session State 可能因為只有處理該 session 的機器才能找到它而造成困難。Server affinity 也有問題——如果透過 IP 位址來維持親和性,代理伺服器後面的所有客戶端可能都被綁定到同一台伺服器。
不同系統類型的建議:
- 公開零售系統:每個 session 資料量不大但有大量閒置使用者 → Database Session State 效能佳
- 租賃系統:需要在每次請求中大量搬運資料 → Server Session State 效能更好
使用者取消:使用者取消或忘記 session 是常見問題。Client Session State 在這方面勝出——使用者離開就自動清除。其他兩種方式需要設定逾時機制來清除已放棄的 session。
系統當機:Database Session State 對三種當機都能合理應對。Server Session State 視 session 物件是否備份到非揮發性儲存而定。Client Session State 不會因客戶端當機而存活,但在伺服器恢復後其餘部分應能繼續運作。
開發難度:Server Session State 通常是最容易開發的,不需在請求間持久化 session state。Database Session State 和 Client Session State 都需要額外的轉換工作。Database Session State 看似不複雜,但額外的開發工作在於將所有其他資料庫使用與 session 資料隔離。
混合使用#
三種方式並非互斥,可以混合使用兩到三種來儲存 session state 的不同部分。不過這通常會使事情更複雜。無論使用哪種組合,只要不是純 Client Session State,都至少需要在客戶端保存一個 session identifier。
作者偏好#
作者偏好 Server Session State(尤其是 memento 可遠端儲存以承受伺服器當機時),也喜歡 Client Session State 用於 session ID 和非常小量的 session 資料。不太喜歡 Database Session State,除非需要 failover 和叢集,且無法儲存遠端 memento 或 session 間的隔離是問題時才考慮。