程序員過關(guān)斬將--解決分布式session問題
session說到 session,我相信每個程序員都不陌生,或多或少在項目中使用過。session 這個詞,其實是一個抽象的概念,它不像 Cookie 那樣有著明確的定義。當(dāng)大多數(shù)程序員談?wù)?session 的時候,可能指的是服務(wù)端存儲數(shù)據(jù)的 session 對象,例如,用戶登錄成功之后把用戶信息存儲在 session 中,類似于這樣的程序。
- Session["UserName"] = new User();
- public class User{
- public int UserId {get ;set ;}
- public string UserName {get ;set;}
- }
而在計算機中,尤其是網(wǎng)絡(luò)應(yīng)用中,session 被定義為“會話”,可以把它看做客戶端和服務(wù)端的一條通道連接,同一個用戶的請求使用同一個 session 會話。在大多數(shù)應(yīng)用中,主要用于用戶的識別,通俗來講,服務(wù)端可以通過 session 來記錄每一個用戶的狀態(tài)信息。那我們就以最常用的服務(wù)端 session 對象來啰嗦幾句
單機 session
session 是存儲在服務(wù)端的,這是一個很重要的概念。這意味著它需要占用服務(wù)器的內(nèi)存,并且它需要一種釋放的機制來保證服務(wù)器內(nèi)存不會被撐爆(例如 LRU)。
在項目初期,為了快速上線,服務(wù)器的部署很多情況下只有一臺服務(wù)器,記錄用戶的登錄狀態(tài)普遍使用 session 機制。請不要說這樣做不合理,至少在項目初期這種做法是最簡單而且最快速的方案。隨著項目的不斷迭代升級,用戶量的不斷增加,你會發(fā)現(xiàn)單機系統(tǒng)成為了項目的最大性能瓶頸,這個時候多數(shù)架構(gòu)師會選擇水平擴展方案。
其實說到底,系統(tǒng)性能的提升都圍繞著一個“分”字,無論是數(shù)據(jù)庫的分庫分表,還是現(xiàn)在興起的微服務(wù),始終在圍繞著一個領(lǐng)域進行切分
當(dāng)單機的 session 機制進行水平擴展就面臨著必須要要解決的問題:session 的親和性(粘性)要怎么樣去解決?
分布式 session
一個單機系統(tǒng)擴展為一個分布式系統(tǒng),就會面臨著分布式 CAP 理論中 AP 和 CP 的選擇
談到分布式 session 的一致性問題,其實主要是要解決用戶 session 的親和性,同一個用戶的請求怎么樣才能保證到達(dá)正確存儲 session 信息的服務(wù)器呢?
session 復(fù)制
最初的方案是采用 session 復(fù)制方案,整體的流程非常簡單:假設(shè)現(xiàn)在有三臺服務(wù)器,當(dāng)一個 session 在其中一臺服務(wù)器上被創(chuàng)建,則同時把這個 session 復(fù)制到其他兩臺服務(wù)器上。這樣當(dāng)用戶的請求無論到達(dá)哪臺服務(wù)器,都會有相應(yīng)的 session 數(shù)據(jù)。
這種方案的優(yōu)勢在于服務(wù)器可以任意水平擴展,每個服務(wù)器都保留著所有的 session 信息,當(dāng)加入一臺服務(wù)器只需要把所有的 session 信息復(fù)制過去即可。但是劣勢更加明顯
- 每個服務(wù)器上都保存著全部的 session 信息,服務(wù)器占用的資源大大增加。
- session 同步需要占用網(wǎng)絡(luò)帶寬,最重要的是如果采用的異步復(fù)制方式,數(shù)據(jù)會有短暫性的不一致,可能會導(dǎo)致用戶訪問失敗。
session 復(fù)制的方案現(xiàn)在已經(jīng)很少有人使用了
負(fù)載均衡方案
當(dāng)一臺服務(wù)器擴展為多臺服務(wù)器,目前最常用的方案是在流量的入口添加負(fù)載均衡器,大體的部署圖是這樣的
image
如果負(fù)載均衡器能夠利用某種手段來實現(xiàn) session 的粘性就能實現(xiàn)分布式 session。目前主流的 nginx 可以根據(jù)“hash_ip”算法將同一個 IP 的請求固定到某臺服務(wù)器,這樣來自于同一個 ip 的 session 請求總是請求到同樣的服務(wù)器。
這種方式比 session 同步方式要好很多,每臺服務(wù)器只存儲對應(yīng)的 session 數(shù)據(jù),這大大節(jié)省了內(nèi)存資源,而且服務(wù)器之間沒有數(shù)據(jù)同步過程。當(dāng)有新服務(wù)器加入的時候,只需要修改負(fù)載均衡器的配置即可,這樣很方便就支持了服務(wù)器水平擴展。但是,同時也面臨著一些不足
服務(wù)器重啟意味著對應(yīng)的 session 信息丟失,這在一些重要的業(yè)務(wù)場景中是不允許的
服務(wù)器的水平擴展需要修改負(fù)載均衡器的配置,修改之后可能會導(dǎo)致之前的 session 重新分布,這樣會導(dǎo)致一部分用戶路由不到正確的 session
session 剝離
現(xiàn)在應(yīng)用更廣泛的分布式 session 技術(shù)是把 session 數(shù)據(jù)徹底從業(yè)務(wù)服務(wù)器中剝離,單獨存儲在其他外部設(shè)備中,而這些外部設(shè)備可以采用主備或者主從,甚至集群的模式來達(dá)到高可用。比如現(xiàn)在最常用的方案是把 session 數(shù)據(jù)存儲在 redis 中,雖然從 redis 讀寫 session 數(shù)據(jù)需要花費一定的網(wǎng)絡(luò)耗時,但是對于一般的應(yīng)用來說在可以接受范圍之內(nèi)。
這種方案好處是整體架構(gòu)更加清晰,也更加靈活,應(yīng)用的服務(wù)器整體擴展能力再也不用考慮 session 的影響,而 session 的問題被轉(zhuǎn)移到外部設(shè)備,通常可以利用內(nèi)存性 NOSql 來解決性能問題,而這些外部設(shè)備一般都會有對應(yīng)的分布式集群方案,例如 redis,可以利用主從或者哨兵模式甚至集群來提供更大規(guī)模的數(shù)據(jù)支撐能力。
Actor 模型
Actor 模型解決這種用戶粘性問題會更加優(yōu)雅,它天生就自帶了對象識別功能,簡單來說,同一個 key 的請求,總能到達(dá)正確的 actor 實例,這不是我們想要的結(jié)果嗎?而且 actor 模型下不用加鎖就能處理并發(fā)問題,為什么沒人用呢?而且采用 acotr 模型就可以利用進程內(nèi)緩存的形式,比請求局域網(wǎng) redis 的網(wǎng)絡(luò)延遲要低很多。
本文轉(zhuǎn)載自微信公眾號「 架構(gòu)師修行之路」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系 架構(gòu)師修行之路公眾號