解決分佈式session問題

session

說到 session,我相信每一個程序員都不陌生,或多或少在項目中使用過。session 這個詞,實際上是一個抽象的概念,它不像 Cookie 那樣有着明確的定義。當大多數程序員談論 session 的時候,可能指的是服務端存儲數據的 session 對象,例如,用戶登陸成功以後把用戶信息存儲在 session 中,相似於這樣的程序nginx

Session["UserName"] = new User();

 public class User{
     public int UserId {get ;set ;}
     public string UserName {get ;set;}

 }

而在計算機中,尤爲是網絡應用中,session 被定義爲「會話」,能夠把它看作客戶端和服務端的一條通道鏈接,同一個用戶的請求使用同一個 session 會話。在大多數應用中,主要用於用戶的識別,通俗來說,服務端能夠經過 session 來記錄每個用戶的狀態信息。那咱們就以最經常使用的服務端 session 對象來囉嗦幾句程序員

單機 session

session 是存儲在服務端的,這是一個很重要的概念。這意味着它須要佔用服務器的內存,而且它須要一種釋放的機制來保證服務器內存不會被撐爆(例如 LRU)。redis

在項目初期,爲了快速上線,服務器的部署不少狀況下只有一臺服務器,記錄用戶的登陸狀態廣泛使用 session 機制。請不要說這樣作不合理,至少在項目初期這種作法是最簡單並且最快速的方案。隨着項目的不斷迭代升級,用戶量的不斷增長,你會發現單機系統成爲了項目的最大性能瓶頸,這個時候多數架構師會選擇水平擴展方案。算法

其實說到底,系統性能的提高都圍繞着一個「分」字,不管是數據庫的分庫分表,仍是如今興起的微服務,始終在圍繞着一個領域進行切分

當單機的 session 機制進行水平擴展就面臨着必需要要解決的問題:session 的親和性(粘性)要怎麼樣去解決?數據庫

分佈式 session

一個單機系統擴展爲一個分佈式系統,就會面臨着分佈式 CAP 理論中 AP 和 CP 的選擇,具體能夠查看以前的文章:設計模式

晦澀難懂的 CAP,是否徹底正確?緩存

談到分佈式 session 的一致性問題,其實主要是要解決用戶 session 的親和性,同一個用戶的請求怎麼樣才能保證到達正確存儲 session 信息的服務器呢?服務器

session 複製

最初的方案是採用 session 複製方案,總體的流程很是簡單:假設如今有三臺服務器,當一個 session 在其中一臺服務器上被建立,則同時把這個 session 複製到其餘兩臺服務器上。這樣當用戶的請求不管到達哪臺服務器,都會有相應的 session 數據。網絡

這種方案的優點在於服務器能夠任意水平擴展,每一個服務器都保留着全部的 session 信息,當加入一臺服務器只須要把全部的 session 信息複製過去便可。可是劣勢更加明顯session

  • 每一個服務器上都保存着所有的 session 信息,服務器佔用的資源大大增長。
  • session 同步須要佔用網絡帶寬,最重要的是若是採用的異步複製方式,數據會有短暫性的不一致,可能會致使用戶訪問失敗。

session 複製的方案如今已經不多有人使用了

負載均衡方案

當一臺服務器擴展爲多臺服務器,目前最經常使用的方案是在流量的入口添加負載均衡器,大致的部署圖是這樣的

image

若是負載均衡器可以利用某種手段來實現 session 的粘性就能實現分佈式 session。目前主流的 nginx 能夠根據「hash_ip」算法將同一個 IP 的請求固定到某臺服務器,這樣來自於同一個 ip 的 session 請求老是請求到一樣的服務器。

這種方式比 session 同步方式要好不少,每臺服務器只存儲對應的 session 數據,這大大節省了內存資源,並且服務器之間沒有數據同步過程。當有新服務器加入的時候,只須要修改負載均衡器的配置便可,這樣很方便就支持了服務器水平擴展。可是,同時也面臨着一些不足

  • 服務器重啓意味着對應的 session 信息丟失,這在一些重要的業務場景中是不容許的
  • 服務器的水平擴展須要修改負載均衡器的配置,修改以後可能會致使以前的 session 從新分佈,這樣會致使一部分用戶路由不到正確的 session
session 剝離

如今應用更普遍的分佈式 session 技術是把 session 數據完全從業務服務器中剝離,單獨存儲在其餘外部設備中,而這些外部設備能夠採用主備或者主從,甚至集羣的模式來達到高可用。好比如今最經常使用的方案是把 session 數據存儲在 redis 中,雖然從 redis 讀寫 session 數據須要花費必定的網絡耗時,可是對於通常的應用來講在能夠接受範圍以內。

這種方案好處是總體架構更加清晰,也更加靈活,應用的服務器總體擴展能力不再用考慮 session 的影響,而 session 的問題被轉移到外部設備,一般能夠利用內存性 NOSql 來解決性能問題,而這些外部設備通常都會有對應的分佈式集羣方案,例如 redis,能夠利用主從或者哨兵模式甚至集羣來提供更大規模的數據支撐能力。

image

Actor 模型

不多有人會說起 Actor 模型,我在以前的文章中介紹過 actor 模型,你們能夠去看一下

分佈式高併發下 Actor 模型如此優秀

Actor 模型解決這種用戶粘性問題會更加優雅,它天生就自帶了對象識別功能,簡單來講,同一個 key 的請求,總能到達正確的 actor 實例,這不是咱們想要的結果嗎?並且 actor 模型下不用加鎖就能處理併發問題,爲何沒人用呢?並且採用 acotr 模型就能夠利用進程內緩存的形式,比請求局域網 redis 的網絡延遲要低不少。

寫在最後

固然若是隻是針對用戶登陸這個應用場景,session 方案並非惟一的解決方案,能夠參考菜菜以前的文章

程序員過關斬將--更加優雅的 Token 認證方式 JWT

寫在最後

每一個問題的解決方案有不少,沒有完美的方案,只有最適合業務場景的方案。認清技術的本質,纔是咱們提升自身技能的捷徑。能力有限,技術無限,歡迎批評指正!

更多精彩文章

image

相關文章
相關標籤/搜索