本文長度為3056字,預計讀完需1.1MB流量,建議閱讀8分鐘。
這篇是《分散式關注點系列》中「負載均衡」相關的內容最後一發了,後續也會繼續講「高可用」相關的其它主題,主要是限流、降級、熔斷之類的吧,具體還沒定。文末先附上之前發過的高可用相關文章,供你再溫故一下。
下面這個場景不知是否在你面前出現過。
開發Z哥對運維Y弟喊:“Y弟,現在系統好卡,剛上了一波活動,趕緊幫我加幾臺機器上去頂一下。”
Y弟回覆說:“沒問題,分分鐘搞定”。
然後就發現資料庫的壓力迅速上升,DBA就吼了:“Z哥,你丫的搞什麼呢?資料庫要被你弄垮了”。
然後客服那邊接框也爆炸了,越來越多的使用者說剛登陸後沒多久,操作著就退出了,接著登陸,又退出了,到底還做不做生意了。
這些問題背後都是由於一個「Session丟失」問題導致的。
一、什麼是Session丟失
相信Session對大部分Coder來說應該都知道。它是為了將同一個使用者的多次訪問在系統中被識別為“同一個使用者”而產生的概念。除此之外,還可以基於它來減少重複往DB或者遠端服務處獲取與該使用者相關的資訊,以起到提升效能的作用。
在我們做了負載均衡的場景中,如果選擇的負載策略是hash策略,那麼會使得Session產生一個副作用,這個副作用就如上面舉的案例那樣,使用者一旦由於某種原因從原先訪問伺服器A變成訪問伺服器B,就會出現“登陸狀態丟失”、“快取穿透”等問題。
為什麼hash策略會出現這個問題呢?首先有必要先了解一下hash是如何進行的。hash策略就是下圖這樣的一個雜湊函式。在函式不變的情況下,A永遠對應01,B對應04,C對應08。
▲圖片來源於網路,版權歸原作者所有
以nginx中的ip_hash策略來舉個例子。因為我們認為正常情況下使用者的ip不會在短時間內發生變化,所以當我們選擇使用ip_hash策略進行負載均衡時,意味著期望同一個使用者能夠一直訪問到同一臺伺服器上,就像下圖這樣。
▲圖中的hash函式是最簡單的隨意舉例
如此一來,我們只需要在這一臺伺服器上將這個使用者相關的資訊快取在程式內,就能起到非常高價效比的提升效能的效果。
這時,客戶端與服務端之間的相當於建立了一個信任,相互認識。這個信任就是「Session」。
但是,當我們加了一臺伺服器之後,事情就發生變化了。
▲圖中的hash函式是最簡單的隨意舉例
這個時候我們原先的預期就被破壞了。因為使用者與序號0節點的連結變成了與序號3的連結,所以產生了前面提到的「Session丟失」問題。與此同時,在序號0節點上做的程式內快取都無效了,而在序號3節點上又沒有使用者相關的任何快取,導致大量資料需要從下游的DB或者遠端服務處獲取。你要知道,一旦涉及到網路通訊,效能必然明顯下降,I/O、序列化都是耗時的工作。更重要的是,一旦同時有大量使用者產生這個情況,由於後端的DB和遠端服務瞬時無法承載激增的高密度請求,可能會導致它掛起。這還沒完,如果當前程式沒有一些故障隔離或者降級策略,還會進一步產生蝴蝶效應,導致整個大系統響應緩慢。可謂“一顆老鼠屎壞了一鍋粥”。
二、nginx是如何來解決這個問題的
既然以nginx舉例,還是從nginx開始聊。通過在nginx中引入nginx-sticky-module模組可以來解決這個問題。解決的整個過程如下。
▲圖片來源於網路,版本歸原作者所有
可以看到,當client第一次進入到nginx匹配節點的時候,在給它分配一個節點的同時,會將這個節點的唯一標識進行md5後寫入到cookie中一併返回,如果下次再發起請求的時候發現帶有這個cookie值,就直接轉發到該值所對應的節點上去。這個機制被專業的稱之為「Session保持」。
雖然可以利用cookie來解決這個問題,但是cookie也有一個潛在的問題,如果客戶端未開啟cookie功能,這個機制就失效了。不過好在目前主流瀏覽器都是預設開啟cookie的。
題外話:nginx是2004年釋出的,在nginx-sticky-module出現之前的7年間也是nginx相比競品HAProxy最大的一個短板,因為HAProxy支援Session保持。
三、Session保持的其它方案
除了cookie之外,還有2種方式也可以最終達到類似的效果。分別被稱為「Session複製」、「Session共享」。
01 Session複製
這是最簡單粗暴的方式。根據第一節的案例來看,導致問題的原因是節點3沒有使用者的Session。那麼很容易想到,在節點3執行之前把Session相關的Cache資料複製過去唄。並且在多個節點之間持續保證資料的同步,也就是說,每一臺節點上都存在每個使用者的Session資料。
實現的方案有很多,特別是不同的宿主程式都或多或少提供了一些切入點,甚至是拿來即用的方案,如Tomcat的Delta Manager和Backup Manager、Tomcat和IIS的Filter機制等等,這裡就不展開了。
此類方案的特點是
優點:天然高可用,一部分節點當機沒事。因為每一個節點上存放著所有已連線使用者的會話資訊。
缺點:因為每臺計算機的記憶體是有上限的,僅適用於會話相關的資料大小較小的場景。並且,由於多個節點之間需要同步資料,需要額外解決資料一致性問題。與此同時,隨著節點越多,損耗越大(延遲、頻寬等),有廣播風暴風險。
02 Session共享
我們還可以通過將session資訊存放到全域性共享的儲存介質中來達到一樣的效果,如資料庫、遠端快取等,這是一種中心化思想的解決方案。
此類方案的特點是
優點:不管節點怎麼增加和減少,100%不會產生會話丟失。
缺點:每次讀寫請求都需要增加額外共享儲存呼叫,增加了網路I/O、序列化等操作,效能明顯下降。另外,用作共享的儲存介質除了增加了額外的維護成本外,還需要解決單點問題,以免產生系統性風險。
同之前「Session保持」方案一起對比下各自的優缺點和適用場景。
分別用一句話概括一下這3個方案:
Session 保持。原來在哪還是去哪。
Session 複製。不管在哪都有一樣的資料。
Session 共享。所有節點共用一份資料。
越大型的系統,最終都會往「Session共享」這個方案上走,因為只要再對這個共享儲存做橫向擴充套件,理論上就可以支撐無窮大的使用者了。如Redis、一系列的NOSQL以及NEWSQL等。就像下面這樣,集「規模大」、「高可用」、「效果好」於一身。
四、結語
現在你應該清楚了Session丟失問題,也知道了如何去應對他。但是,我們還需要明白一個事實:嚴格來說「Session保持」本質上是破壞了做「負載均衡」的初衷。舉個極端點的場景:一共有10個會話連在了節點A上,並且都是活動中狀態。那麼這個時候哪怕增加一個節點B上線,只要沒有新的會話進來,節點B上的活動連線數永遠是0,並沒有起到分擔壓力的作用。
但是,在系統的起步時期,其實用這樣簡單的方案也是極好的。
相關文章:
▶ 關於作者:張帆(Zachary,個人微訊號:Zachary-ZF)。堅持用心打磨每一篇高質量原創。
微信公眾號(首發):跨界架構師。<-- 點選後閱讀熱門文章
定期發表原創內容:架構設計丨分散式系統丨產品丨運營丨一些深度思考。