分佈式系統 服務單點問題的探討

在分佈式系統中,單點問題是一個比較常見的問題,對於單點問題可以分爲有狀態服務的單點問題和無狀態服務的單點問題。

無狀態服務的單點問題

對於無狀態的服務,單點問題的解決比較簡單,因爲服務是無狀態的,所以服務節點很容易進行平行擴展。比如,在分佈式系統中,爲了降低各進程通信的網絡結構的複雜度,我們會增加一個代理節點,專門做消息的轉發,其他的業務進行直接和代理節點進行通信,類似一個星型的網絡結構。

參考上面兩個圖,圖中proxy是一個消息轉發代理,業務進程中的消息都會經過該代理,這也是比較場景的一個架構。在上圖中,只有一個proxy,如果該節點掛了,那麼所有的業務進程之間都無法進行通信。由於proxy是無狀態的服務,所以很容易想到第二個圖中的解決方案,增加一個proxy節點,兩個proxy節點是對等的。增加新節點後,業務進程需要與兩個Proxy之間增加一個心跳的機制,業務進程在發送消息的時候根據proxy的狀態,選擇一個可用的proxy進行消息的傳遞。從負載均衡的角度來看,如果兩個proxy都是存活狀態的話,業務進程應當隨機選擇一個proxy。

那麼該解決方案中會存在什麼問題呢?
主要存在的問題是消息的順序性問題。一般來說,業務的消息都是發送、應答,再發送、再應答這樣的順序進行的,在業務中可以保證消息的順序性。但是,在實際的應用中,會出現這樣一個情況:在業務進程1中,有個業務需要給業務進程3發送消息A和消息B,根據業務的特性,消息A必須要在消息B之前到達。如果業務進程1在發送消息A的時候選擇了proxy1,在發送消息B的時候選擇了proxy2,那麼在分佈式環境中,我們並不能確保先發送的消息A一定就能比後發送的消息B先到達業務進程3。那麼怎麼解決這個問題?其實方案也比較簡單,對於這類對消息順序有要求的業務,我們可以指定對應的proxy進行發送,比如消息A和消息B都是使用proxy1進行發送,這樣就可以保證消息A比消息B先到達業務進程3。
整體來說,對於無狀態的服務的單點問題的解決方案還是比較簡單的,只要增加對應的服務節點即可。

有狀態服務的單點問題

相對無狀態服務的單點問題,有狀態服務的單點問題就複雜多了。

如果在架構中,有個節點是單點的,並且該節點是有狀態的服務,那麼首先要考慮的是該節點是否可以去狀態,如果可以,則優先選擇去除狀態的方案(比如說把狀態存儲到後端的可靠DB中,可能存在性能的損耗),然後就退化成了一個無狀態服務的單點問題了,這就可以參考上一節的方案了。
但是,並不是所有的服務都是可以去狀態的,比如說對於一些業務它只能在一個節點中進行處理,如果在不同的節點中處理的話可能會造成狀態的不一致,這類型的業務是無法去除狀態的。對於這種無法去除狀態的單點的問題的解決方案也是有多種,但是越完善的方案實現起來就越複雜,不過整體的思路都是採用主備的方式。這種通常是一臺主機、一臺或多臺備機,在正常情況下主機對外提供服務,並把數據同步到備機,當主機宕機後,備機立刻開始服務。

第一個方案就是就是增加一個備用節點,備用節點和業務進程也可以進行通信,但是所有的業務消息都發往Master節點進行處理。Master節點和Slave節點之間採用ping的方式進行通信。Slave節點會定時發送ping包給Master節點,Master節點收到後會響應一個Ack包。當Slave節點發現Master節點沒有響應的時候,就會認爲Master節點掛了,然後把自己升級爲Master節點,並且通知業務進程把消息轉發給自己。

該方案看起來也是挺完美的,好像不存在什麼問題,Slave升級爲Master後所有的業務消息都會發給它。但是,如果在Master內部有一些自己的業務邏輯,比如說隨機生成一些業務數據,並且定時存檔。那麼當Master和Slave之間的網絡出現問題的時候,Slave會認爲Master掛了,就會升級爲Master,同樣會執行Master的相應的業務邏輯,同樣也會生成一些業務數據回寫到DB。但是,其實Master是沒有掛的,它同樣也在運行對應的業務邏輯(即使業務進程的消息沒有發給舊的Master了),這樣就會出現兩個Master進行寫同一份數據了,造成數據的混亂。所以說,該方案並不是一個很好的方案。

那麼怎麼解決可能會出現多個Master的問題?
換個角度看,該問題其實就是怎麼去裁決,哪個節點是Master的問題。
方案一:引入第三方的服務進行裁決。
我們可以引入ZooKeeper,由ZooKeeper進行裁決。同樣,我們啓動兩個主節點,“節點A”和節點B。它們啓動之後向ZooKeeper去註冊一個節點,假設節點A註冊的節點爲master001,節點B註冊的節點爲master002,註冊完成後進行選舉,編號小的節點爲真正的主節點。那麼,通過這種方式就完成了對兩個Master進程的調度。


 

ZooKeeper
ZooKeeper有一套機制,可以保證不會出現多個Master的情況,具體可以參考:

https://segmentfault.com/a/1190000012185322

方案二: 通過選舉算法和租約的方式實現Master的選舉
對於方案一的缺點主要要多維護一套ZooKeeper的服務,如果原本業務上並沒有部署該服務的話,要增加該服務的維護也是比較麻煩的事情。這個時候我們可以在業務進程中加入Master的選舉方案。目前有比較成熟的選舉算法,比如Paxos和Raft。然後再配合租約機制,就可以實現Master的選舉,並且確保當前只有一個Master的方案。但是,這些選舉算法理解起來並不是那麼地容易,要實現一套完善的方案也是挺難的。所以不建議重複造輪子,業內有很多成熟的框架或者組件可以使用,比如微信的PhxPaxos。

比如上圖的方案中,三個節點其實都是對等的,通過選舉算法確定一個Master。爲了確保任何時候都只能存在一個Matster,需要加入租約的機制。一個節點成爲Master後,Master和非Master節點都會進行計時,在超過租約時間後,三個節點後可以發起“我要成爲Master”的請求,進行重新選舉。由於三個節點都是對等的,任意一個都可以成爲Master,也就是說租期過後,有可能會出現Master切換的情況,所以爲了避免Master的頻繁切換,Master節點需要比另外兩個節點先發起自己要成爲Master的請求(續租),告訴其他兩個節點我要繼續成爲Master,然後另外兩個節點收到請求後會進行應答,正常情況下另外兩個節點會同意該請求。關鍵點就是,在租約過期之前,非Master節點不能發起“我要成爲Master”的請求,這樣就可以解決Master頻繁切換的問題。


主從另一個目的是進行讀寫分離,這是當單機讀寫壓力過高的一種通用型解決方案。 其主機的角色只提供寫操作或少量的讀,把多餘讀請求通過負載均衡算法分流到單個或多個slave服務器上。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章