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