做了“負(fù)載均衡”就可以隨便加機器了嗎?
前面的一篇分享《如何搭建應(yīng)對億級流量的高可用負(fù)載均衡?》相信大家看完后對負(fù)載均衡的應(yīng)用有了一些了解。這篇主要為大家解答做了“負(fù)載均衡”是否能隨便加機器。
下面這個場景不知是否在你面前出現(xiàn)過:
開發(fā)Z哥對運維Y弟喊:“Y弟,現(xiàn)在系統(tǒng)好卡,剛上了一波活動,趕緊幫我加幾臺機器上去頂一下。”
Y弟回復(fù)說:“沒問題,分分鐘搞定”。
然后就發(fā)現(xiàn)數(shù)據(jù)庫的壓力迅速上升,DBA就吼了:“Z哥,你丫的搞什么呢?數(shù)據(jù)庫要被你弄垮了”。
然后客服那邊接框也爆炸了,越來越多的用戶說剛登陸后沒多久,操作著就退出了,接著登陸,又退出了,到底還做不做生意了。
這些問題背后都是由于一個「Session丟失」問題導(dǎo)致的。
什么是Session丟失
相信Session對大部分Coder來說應(yīng)該都知道。它是為了將同一個用戶的多次訪問在系統(tǒng)中被識別為“同一個用戶”而產(chǎn)生的概念。除此之外,還可以基于它來減少重復(fù)往DB或者遠(yuǎn)程服務(wù)處獲取與該用戶相關(guān)的信息,以起到提升性能的作用。
在我們做了負(fù)載均衡的場景中,如果選擇的負(fù)載策略是hash策略,那么會使得Session產(chǎn)生一個副作用,這個副作用就如上面舉的案例那樣,用戶一旦由于某種原因從原先訪問服務(wù)器A變成訪問服務(wù)器B,就會出現(xiàn)“登陸狀態(tài)丟失”、“緩存穿透”等問題。
為什么hash策略會出現(xiàn)這個問題呢?首先有必要先了解一下hash是如何進(jìn)行的。hash策略就是下圖這樣的一個散列函數(shù)。在函數(shù)不變的情況下,A永遠(yuǎn)對應(yīng)01,B對應(yīng)04,C對應(yīng)08。
以nginx中的ip_hash策略來舉個例子。因為我們認(rèn)為正常情況下用戶的ip不會在短時間內(nèi)發(fā)生變化。
所以當(dāng)我們選擇使用ip_hash策略進(jìn)行負(fù)載均衡時,意味著期望同一個用戶能夠一直訪問到同一臺服務(wù)器上,就像下圖這樣:
如此一來,我們只需要在這一臺服務(wù)器上將這個用戶相關(guān)的信息緩存在進(jìn)程內(nèi),就能起到非常高性價比的提升性能的效果。
這時,客戶端與服務(wù)端之間的相當(dāng)于建立了一個信任,相互認(rèn)識。這個信任就是「Session」。
但是,當(dāng)我們加了一臺服務(wù)器之后,事情就發(fā)生變化了,圖中的hash函數(shù)是最簡單的隨意舉例。
這個時候我們原先的預(yù)期就被破壞了。因為用戶與序號0節(jié)點的鏈接變成了與序號3的鏈接,所以產(chǎn)生了前面提到的「Session丟失」問題。
與此同時,在序號0節(jié)點上做的進(jìn)程內(nèi)緩存都無效了,而在序號3節(jié)點上又沒有用戶相關(guān)的任何緩存,導(dǎo)致大量數(shù)據(jù)需要從下游的DB或者遠(yuǎn)程服務(wù)處獲取。
你要知道,一旦涉及到網(wǎng)絡(luò)通信,性能必然明顯下降,I/O、序列化都是耗時的工作。
更重要的是,一旦同時有大量用戶產(chǎn)生這個情況,由于后端的DB和遠(yuǎn)程服務(wù)瞬時無法承載激增的高密度請求,可能會導(dǎo)致它掛起。
這還沒完,如果當(dāng)前程序沒有一些故障隔離或者降級策略,還會進(jìn)一步產(chǎn)生蝴蝶效應(yīng),導(dǎo)致整個大系統(tǒng)響應(yīng)緩慢??芍^“一顆老鼠屎壞了一鍋粥”。
Nginx如何來解決這個問題的
既然以nginx舉例,還是從nginx開始聊。通過在nginx中引入nginx-sticky-module模塊可以來解決這個問題。
可以看到,當(dāng)client第一次進(jìn)入到nginx匹配節(jié)點的時候,在給它分配一個節(jié)點的同時,會將這個節(jié)點的唯一標(biāo)識進(jìn)行md5后寫入到cookie中一并返回。
如果下次再發(fā)起請求的時候發(fā)現(xiàn)帶有這個cookie值,就直接轉(zhuǎn)發(fā)到該值所對應(yīng)的節(jié)點上去。這個機制被專業(yè)的稱之為「Session保持」。
雖然可以利用cookie來解決這個問題,但是cookie也有一個潛在的問題,如果客戶端未開啟cookie功能,這個機制就失效了。不過好在目前主流瀏覽器都是默認(rèn)打開cookie的。
題外話:nginx是2004年發(fā)布的,在nginx-sticky-module出現(xiàn)之前的7年間也是nginx相比競品HAProxy最大的一個短板,因為HAProxy支持Session保持。
Session保持的其它方案
除了cookie之外,還有2種方式也可以最終達(dá)到類似的效果。分別被稱為「Session復(fù)制」、「Session共享」。
Session復(fù)制
這是最簡單粗暴的方式。根據(jù)第一節(jié)的案例來看,導(dǎo)致問題的原因是節(jié)點3沒有用戶的Session。
那么很容易想到,在節(jié)點3運行之前把Session相關(guān)的Cache數(shù)據(jù)復(fù)制過去唄。
并且在多個節(jié)點之間持續(xù)保證數(shù)據(jù)的同步,也就是說,每一臺節(jié)點上都存在每個用戶的Session數(shù)據(jù)。
實現(xiàn)的方案有很多,特別是不同的宿主程序都或多或少提供了一些切入點,甚至是拿來即用的方案,如Tomcat的Delta Manager和Backup Manager、Tomcat和IIS的Filter機制等等,這里就不展開了。
此類方案的特點是:
- 優(yōu)點:天然高可用,一部分節(jié)點宕機沒事。因為每一個節(jié)點上存放著所有已連接用戶的會話信息。
- 缺點:因為每臺計算機的內(nèi)存是有上限的,僅適用于會話相關(guān)的數(shù)據(jù)大小較小的場景。并且,由于多個節(jié)點之間需要同步數(shù)據(jù),需要額外解決數(shù)據(jù)一致性問題。與此同時,隨著節(jié)點越多,損耗越大(延遲、帶寬等),有廣播風(fēng)暴風(fēng)險。
Session共享
我們還可以通過將session信息存放到全局共享的存儲介質(zhì)中來達(dá)到一樣的效果,如數(shù)據(jù)庫、遠(yuǎn)程緩存等,這是一種中心化思想的解決方案。
此類方案的特點是
- 優(yōu)點:不管節(jié)點怎么增加和減少,100%不會產(chǎn)生會話丟失。
- 缺點:每次讀寫請求都需要增加額外共享儲存調(diào)用,增加了網(wǎng)絡(luò)I/O、序列化等操作,性能明顯下降。
另外,用作共享的存儲介質(zhì)除了增加了額外的維護(hù)成本外,還需要解決單點問題,以免產(chǎn)生系統(tǒng)性風(fēng)險。
同之前「Session保持」方案一起對比下各自的優(yōu)缺點和適用場景。
分別用一句話概括一下這3個方案:
- Session 保持。原來在哪還是去哪。
- Session 復(fù)制。不管在哪都有一樣的數(shù)據(jù)。
- Session 共享。所有節(jié)點共用一份數(shù)據(jù)。
越大型的系統(tǒng),最終都會往「Session共享」這個方案上走,因為只要再對這個共享存儲做橫向擴(kuò)展,理論上就可以支撐無窮大的用戶了。
如Redis、一系列的NOSQL以及NEWSQL等。就像下面這樣,集「規(guī)模大」、「高可用」、「效果好」于一身。
結(jié)語
現(xiàn)在你應(yīng)該清楚了Session丟失問題,也知道了如何去應(yīng)對他。但是,我們還需要明白一個事實:嚴(yán)格來說「Session保持」本質(zhì)上是破壞了做「負(fù)載均衡」的初衷。舉個極端
舉個極端點的場景:一共有10個會話連在了節(jié)點A上,并且都是活動中狀態(tài)。
那么這個時候哪怕增加一個節(jié)點B上線,只要沒有新的會話進(jìn)來,節(jié)點B上的活動連接數(shù)永遠(yuǎn)是0,并沒有起到分擔(dān)壓力的作用。但是,在系統(tǒng)的起步時期,其實用這樣簡單的方案也是極好的。