從0開始學架構——05複雜度來源高可用

今天,我們聊聊複雜度的第二個來源高可用。

參考維基百科,先來看看高可用的定義。

系統無中斷地執行其功能的能力,代表系統的可用性程度,是進行系統設計時的準則之一。

這個定義的關鍵在於“無中斷”,但恰好難點也在“無中斷”上面,因為無論是單個硬件還是單個軟件,都不可能做到無中斷,硬件會出故障,軟件會有 bug;硬件會逐漸老化,軟件會越來越複雜和龐大……

除了硬件和軟件本質上無法做到“無中斷”,外部環境導致的不可用更加不可避免、不受控制。例如,斷電、水災、地震,這些事故或者災難也會導致系統不可用,而且影響程度更加嚴重,更加難以預測和規避。

所以,系統的高可用方案五花八門,但萬變不離其宗,本質上都是通過“冗餘” 來實現高可用。通俗點來講,就是一臺機器不夠就兩臺,兩臺不夠就四臺;一個機房可能斷電,那就部署兩個機房;一條通道可能故障,那就用兩條,兩條不夠那就用三條(移動、電信、聯通一起上)。高可用的“冗餘”解決方案,單純從形式上來看,和之前講的高性能是一樣的,都是通過增加更多機器來達到目的,但其實本質上是有根本區別的:高性能增加機器目的在於“擴展”處理性能;高可用增加機器目的在於“冗餘”處理單元。

通過冗餘增強了可用性,但同時也帶來了複雜性,我會根據不同的應用場景逐一分析。

計算高可用

這裡的“計算”指的是業務的邏輯處理。計算有一個特點就是無論在哪臺機器上進行計算,同樣的算法和輸入數據,產出的結果都是一樣的,所以將計算從一臺機器遷移到另外一臺機器,對業務並沒有什麼影響。既然如此,計算高可用的複雜度體現在哪裡呢?我以最簡單的單機變雙機為例進行分析。先來看一個單機變雙機的簡單架構示意圖。

你可能會發現,這個雙機的架構圖和上期“高性能”講到的雙機架構圖是一樣的,因此複雜度也是類似的,具體表現為:

•需要增加一個任務分配器,選擇合適的任務分配器也是一件複雜的事情,需要綜合考慮性能、成本、可維護性、可用性等各方面因素。

•任務分配器和真正的業務服務器之間有連接和交互,需要選擇合適的連接方式,並且對連接進行管理。例如,連接建立、連接檢測、連接中斷後如何處理等。

•任務分配器需要增加分配算法。例如,常見的雙機算法有主備、主主,主備方案又可以細分為冷備、溫備、熱備。

上面這個示意圖只是簡單的雙機架構,我們再看一個複雜一點的高可用集群架構。

這個高可用集群相比雙機來說,分配算法更加複雜,可以是 1 主 3 備、2 主 2 備、3 主 1 備、4 主 0 備,具體應該採用哪種方式,需要結合實際業務需

求來分析和判斷,並不存在某種算法就一定優於另外的算法。例如,

ZooKeeper 採用的就是 1 主多備,而 Memcached 採用的就是全主 0 備。

存儲高可用

對於需要存儲數據的系統來說,整個系統的高可用設計關鍵點和難點就在於“存儲高可用”。存儲與計算相比,有一個本質上的區別:將數據從一臺機器搬到到另一臺機器,需要經過線路進行傳輸。線路傳輸的速度是毫秒級別,同一機房內部能夠做到幾毫秒;分佈在不同地方的機房,傳輸耗時需要幾十甚至上百毫秒。例如,從廣州機房到北京機房,穩定情況下 ping 延時大約是 50ms,不穩定情況下可能達到 1s 甚至更多。

雖然毫秒對於人來說幾乎沒有什麼感覺,但是對於高可用系統來說,就是本質上的不同,這意味著整個系統在某個時間點上,數據肯定是不一致的。按照“數據 + 邏輯 = 業務”這個公式來套的話,數據不一致,即使邏輯一致,最後的業務表現就不一樣了。以最經典的銀行儲蓄業務為例,假設用戶的數據存在北京機房,用戶存入了 1 萬塊錢,然後他查詢的時候被路由到了上海機房,北京機房的數據沒有同步到上海機房,用戶會發現他的餘額並沒有增加 1 萬塊。想象一下,此時用戶肯定會背後一涼,馬上會懷疑自己的錢被盜了,然後趕緊打客服電話投訴,甚至打 110 報警,即使最後發現只是因為傳輸延遲導致的問題,站在用戶的角度來說,這個過程的體驗肯定很不好。

除了物理上的傳輸速度限制,傳輸線路本身也存在可用性問題,傳輸線路可能中斷、可能擁塞、可能異常(錯包、丟包),並且傳輸線路的故障時間一般都特別長,短的十幾分鍾,長的幾個小時都是可能的。例如,2015 年支付寶因為光纜被挖斷,業務影響超過 4 個小時;2016 年中美海底光纜中斷 3 小時等。在傳輸線路中斷的情況下,就意味著存儲無法進行同步,在這段時間內整個系統的數據是不一致的。

綜合分析,無論是正常情況下的傳輸延遲,還是異常情況下的傳輸中斷,都會導致系統的數據在某個時間點或者時間段是不一致的,而數據的不一致又會導致業務問題;但如果完全不做冗餘,系統的整體高可用又無法保證,所以存儲高可用的難點不在於如何備份數據,而在於如何減少或者規避數據不一致對業務造成的影響。

分佈式領域裡面有一個著名的 CAP 定理,從理論上論證了存儲高可用的複雜度。也就是說,存儲高可用不可能同時滿足“一致性、可用性、分區容錯性”,最多滿足其中兩個,這就要求我們在做架構設計時結合業務進行取捨。

高可用狀態決策

無論是計算高可用還是存儲高可用,其基礎都是“狀態決策”,即系統需要能夠判斷當前的狀態是正常還是異常,如果出現了異常就要採取行動來保證高可用。如果狀態決策本身都是有錯誤或者有偏差的,那麼後續的任何行動和處理無論多麼完美也都沒有意義和價值。但在具體實踐的過程中,恰好存在一個本質的矛盾:通過冗餘來實現的高可用系統,狀態決策本質上就不可能做到完全正確。下面我基於幾種常見的決策方式進行詳細分析。

1.獨裁式

獨裁式決策指的是存在一個獨立的決策主體,我們姑且稱它為“決策者”,負責收集信息然後進行決策;所有冗餘的個體,我們姑且稱它為“上報者”,都將狀態信息發送給決策者。

獨裁式的決策方式不會出現決策混亂的問題,因為只有一個決策者,但問題也正是在於只有一個決策者。當決策者本身故障時,整個系統就無法實現準確的

狀態決策。如果決策者本身又做一套狀態決策,那就陷入一個遞歸的死循環了。

2.協商式

協商式決策指的是兩個獨立的個體通過交流信息,然後根據規則進行決策,最常用的協商式決策就是主備決策。

這個架構的基本協商規則可以設計成:

•2 臺服務器啟動時都是備機。

•2 臺服務器建立連接。

•2 臺服務器交換狀態信息。

•某 1 臺服務器做出決策,成為主機;另一臺服務器繼續保持備機身份。

協商式決策的架構不復雜,規則也不復雜,其難點在於,如果兩者的信息交換出現問題(比如主備連接中斷),此時狀態決策應該怎麼做。

•如果備機在連接中斷的情況下認為主機故障,那麼備機需要升級為主機,但實際上此時主機並沒有故障,那麼系統就出現了兩個主機,這與設計初衷(1 主 1 備)是不符合的。

•如果備機在連接中斷的情況下不認為主機故障,則此時如果主機真的發

生故障,那麼系統就沒有主機了,這同樣與設計初衷(1 主 1 備)是不符合的。

•如果為了規避連接中斷對狀態決策帶來的影響,可以增加更多的連接。例如,雙連接、三連接。這樣雖然能夠降低連接中斷對狀態帶來的影響(注意:只能降低,不能徹底解決),但同時又引入了這幾條連接之間信息取捨的問題,即如果不同連接傳遞的信息不同,應該以哪個連接為準?實際上這也是一個無解的答案,無論以哪個連接為準,在特定場景下都可能存在問題。

綜合分析,協商式狀態決策在某些場景總是存在一些問題的。

3.民主式

民主式決策指的是多個獨立的個體通過投票的方式來進行狀態決策。例如,

ZooKeeper 集群在選舉 leader 時就是採用這種方式。

民主式決策和協商式決策比較類似,其基礎都是獨立的個體之間交換信息,每個個體做出自己的決策,然後按照“多數取勝”的規則來確定最終的狀態。不同點在於民主式決策比協商式決策要複雜得多,ZooKeeper 的選舉算法

Paxos,絕大部分人都看得雲裡霧裡,更不用說用代碼來實現這套算法了。

除了算法複雜,民主式決策還有一個固有的缺陷:腦裂。這個詞來源於醫學,指人體左右大腦半球的連接被切斷後,左右腦因為無法交換信息,導致各自做出決策,然後身體受到兩個大腦分別控制,會做出各種奇怪的動作。例如:當一個腦裂患者更衣時,他有時會一隻手將褲子拉起,另一隻手卻將褲子往下脫。腦裂的根本原因是,原來統一的集群因為連接中斷,造成了兩個獨立分隔的子集群,每個子集群單獨進行選舉,於是選出了 2 個主機,相當於人體有兩個大腦了。

從圖中可以看到,正常狀態的時候,節點 5 作為主節點,其他節點作為備節點;當連接發生故障時,節點 1、節點 2、節點 3 形成了一個子集群,節點 4、節點 5 形成了另外一個子集群,這兩個子集群的連接已經中斷,無法進行信息交換。按照民主決策的規則和算法,兩個子集群分別選出了節點 2 和節點

5 作為主節點,此時整個系統就出現了兩個主節點。這個狀態違背了系統設計的初衷,兩個主節點會各自做出自己的決策,整個系統的狀態就混亂了。

為了解決腦裂問題,民主式決策的系統一般都採用“投票節點數必須超過系統總節點數一半”規則來處理。如圖中那種情況,節點 4 和節點 5 形成的子集群總節點數只有 2 個,沒有達到總節點數 5 個的一半,因此這個子集群不會進行選舉。這種方式雖然解決了腦裂問題,但同時降低了系統整體的可用性,即如果系統不是因為腦裂問題導致投票節點數過少,而真的是因為節點故障(例如,節點 1、節點 2、節點 3 真的發生了故障),此時系統也不會選出主節點,整個系統就相當於宕機了,儘管此時還有節點 4 和節點 5 是正常的。

綜合分析,無論採取什麼樣的方案,狀態決策都不可能做到任何場景下都沒有問題,但完全不做高可用方案又會產生更大的問題,如何選取適合系統的高可用方案,也是一個複雜的分析、判斷和選擇的過程。

小結

今天我給你講了複雜度來源之一的高可用,分析了計算高可用和存儲高可用兩個場景,給出了幾種高可用狀態決策方式,希望對你有所幫助。

這就是今天的全部內容,留一道思考題給你吧。高性能和高可用是很多系統的核心複雜度,你認為哪個會更復雜一些?理由是什麼?

歡迎你把答案寫到留言區,和我一起討論。相信經過深度思考的回答,也會讓

你對知識的理解更加深刻。(編輯亂入:精彩的留言有機會獲得豐厚福利哦!)


分享到:


相關文章: