分布式系統設計系列 -- 基本原理及高可用策略


版權聲明:本文為博主原創文章,未經博主同意不得轉載。 https://blog.csdn.net/GugeMichael/article/details/36688043

        ”分布式系統設計“系列第一篇文章,這篇文章主要介紹一些入門的概念和原理。后面帶來一些高可用、數據分布的實踐方法!!


各位親,假設你們覺得本文有還不錯的地方,請點擊“投一票”支持本文,多謝!

http://vote.blog.csdn.net/Article/Details?

articleid=36688043     


==> 分布式系統中的概念
==> 分布式系統與單節點的不同
==> 分布式系統特性
==> 分布式系統設計策略
==> 分布式系統設計實踐


【分布式系統中的概念】

        三元組   

                事實上。分布式系統說白了,就是非常多機器組成的集群,靠彼此之間的網絡通信。擔當的角色可能不同,共同完畢同一個事情的系統。假設按”實體“來划分的話。就是例如以下這幾種:
                1、節點 -- 系統中依照協議完畢計算工作的一個邏輯實體,可能是運行某些工作的進程或機器
                2、網絡 -- 系統的數據傳輸通道。用來彼此通信。通信是具有方向性的。
                3、存儲 -- 系統中持久化數據的數據庫或者文件存儲。



                如圖
                

        狀態特性

                各個節點的狀態能夠是“無狀態”或者“有狀態的”.

                一般覺得。節點是偏計算和通信的模塊,通常是無狀態的。這類應用一般不會存儲自己的中間狀態信息。比方Nginx。普通情況下是轉發請求而已。不會存儲中間信息。還有一種“有狀態”的。如mysql等數據庫。狀態和數據全部持久化到磁盤等介質。

                “無狀態”的節點一般我們覺得是可任意重新啟動的,由於重新啟動后僅僅須要立馬工作就好。

“有狀態”的則不同,須要先讀取持久化的數據,才干開始服務。

所以,“無狀態”的節點通常是能夠任意擴展的,“有狀態”的節點須要一些控制協議來保證擴展。



        系統異常

                異常。可覺得是節點由於某種原因不能工作,此為節點異常。還有由於網絡原因,臨時、永久不能被其它節點所訪問,此為網絡異常。在分布式系統中,要有對異常的處理,保證集群的正常工作。


【分布式系統與單節點的不同】


          1、從linux write()系統調用說起

         眾所周知。在unix/linux/mac(類Unix)環境下,兩個機器通信,最經常使用的就是通過socket連接對方。數據傳輸的話。無非就是調用write()這個系統調用,把一段內存緩沖區發出去。

可是能夠進一步想一下,write()之后能確認對方收到了這些數據嗎?

         答案肯定是不能,原因就是發送數據須要走內核->網卡->鏈路->對端網卡->內核,這一路徑太長了。所以僅僅能是異步操作。write()把數據寫入內核緩沖區之后就返回到應用層了。詳細后面何時發送、怎么發送、TCP怎么做滑動窗體、流控都是tcp/ip協議棧內核的事情了。

         所以在應用層,能確認對方受到了消息僅僅能是對方應用返回數據,邏輯確認了這次發送才覺得是成功的。這就卻別與單系統編程。大部分系統調用、庫調用僅僅要返回了就說明已經確認完畢了。

         2、TCP/IP協議是“不可靠”的

         教科書上明白寫明了互聯網是不可靠的,TCP實現了可靠傳輸。何來“不可靠”呢?先來看一下網絡交互的樣例。有A、B兩個節點,之間通過TCP連接。如今A、B都想確認自己發出的不論什么一條消息都能被對方接收並反饋,於是開始了例如以下操作:
         A->B發送數據,然后A須要等待B收到數據的確認,B收到數據后發送確認消息給A。然后B須要等待A收到數據的確認。A收到B的數據確認消息后再次發送確認消息給B,然后A又去須要等待B收到的確認。。。死循環了!!



         事實上,這就是著名的“拜占庭將軍”問題:

         http://baike.baidu.com/link?url=6iPrbRxHLOo9an1hT-s6DvM5kAoq7RxclIrzgrS34W1fRq1h507RDWJOxfhkDOcihVFRZ2c7ybCkUosWQeUoS_


         所以。通信兩方是“不可能”同一時候確認對方受到了自己的信息

而教科書上定義的事實上是指“單向”通信是成立的,比方A向B發起Http調用,收到了HttpCode 200的響應包。這僅僅能確認。A確認B收到了自己的請求。而且B正常處理了,不能確認的是B確認A受到了它的成功的消息。




         3、不可控的狀態


         在單系統編程中,我們對系統狀態是非常可控的。

比方函數調用、邏輯運算,要么成功。要么失敗。由於這些操作被框在一個機器內部,cpu/總線/內存都是能夠高速得到反饋的。開發人員能夠針對這兩個狀態非常明白的做出程序上的推斷和興許的操作。
         而在分布式的網絡環境下,這就變得微妙了。

比方一次rpc、http調用。可能成功、失敗,還有可能是“超時”,這就比前者的狀態多了一個不可控因素,導致后面的代碼不是非常easy做出推斷。試想一下,用A用支付寶向B轉了一大筆錢,當他按下“確認”后。界面上有個圈在轉啊轉,然后顯示請求超時了,然后A就抓狂了,不知道究竟錢轉沒轉過去,開始確認自己的賬戶、確認B的賬戶、打電話找客服等等。

         所以分布式環境下,我們的事實上要時時刻刻考慮面對這樣的不可控的“第三狀態”設計開發,這也是挑戰之中的一個

         4、視”異常“為”正常“

         單系統下,進程/機器的異常概率十分小。即使出現了問題,能夠通過人工干預重新啟動、遷移等手段恢復。

但在分布式環境下,機器上千台,每幾分鍾都可能出現宕機、死機、網絡斷網等異常。出現的概率非常大。

所以。這樣的環境下,進程core掉、機器掛掉都是須要我們在編程中覺得隨時可能出現的,這樣才干使我們整個系統健壯起來,所以”容錯“是基本需求。

         異常能夠分為例如以下幾類:

         節點錯誤:

                  通常是由於應用導致,一些coredump和系統錯誤觸發,一般又一次服務后可恢復。

        硬件錯誤:

                  由於磁盤或者內存等硬件設備導致某節點不能服務,須要人工干預恢復。

 

        網絡錯誤:

                  由於點對點的網絡抖動,臨時的訪問錯誤,一般拓撲穩定后或流量減小能夠恢復。

        網絡分化:

                  網絡中路由器、交換機錯誤導致網絡不可達。可是網絡兩邊都正常,這類錯誤比較難恢復,而且須要在開發時特別處理。【這樣的情況也會比較前面的問題較難處理】



【分布式系統特性】

         CAP是分布式系統里最著名的理論。wiki百科例如以下

                Consistency(all nodes see the same data at the same time)
                Availability (a guarantee that every request receives a response about whether it was successful or failed)
                Partition tolerance (the system continues to operate despite arbitrary message loss or failure of part of the system)
                                (摘自 :http://en.wikipedia.org/wiki/CAP_theorem)


          早些時候,國外的大牛已經證明了CAP三者是不能兼得,非常多實踐也證明了。
          本人就不挑戰權威了,感興趣的同學能夠自己Google。本人以自己的觀點總結了一下:

          一致性
                    描寫敘述當前全部節點存儲數據的統一模型。分為強一致性和弱一致性:
                    強一致性描寫敘述了全部節點的數據高度一致。不管從哪個節點讀取,都是一樣的。無需操心同一時刻會獲得不同的數據。是級別最高的,實現的代價比較高
                    如圖:
    
                              弱一致性又分為單調一致性和終於一致性:
                              1、單調一致性強調數據是依照時間的新舊。單調向最新的數據靠近。不會回退,如:
                                   數據存在三個版本號v1->v2->v3,獲取僅僅能向v3靠近(如取到的是v2,就不可能再次獲得v1)
                              2、終於一致性強調數據經過一個時間窗體之后,僅僅要多嘗試幾次,終於的狀態是一致的。是最新的數據
                                    如圖:



                              強一致性的場景,就好像交易系統,存取錢的+/-操作必須是立即一致的。否則會令非常多人誤解。


                              弱一致性的場景,大部分就像web互聯網的模式,比方發了一條微博,改了某些配置,可能不會立即生效,但刷新幾次后就能夠看到了。事實上弱一致性就是在系統上通過業務可接受的方式換取了一些系統的低復雜度和可用性。


          可用性
                    保證系統的正常可運行性,在請求方看來,僅僅要發送了一個請求,就能夠得到恢復不管成功還是失敗(不會超時)!


         分區容忍性
                    在系統某些節點或網絡有異常的情況下,系統依然能夠繼續服務。


                    這通常是有負載均衡和副本來支撐的。比如計算模塊異常可通過負載均衡引流到其它平行節點。存儲模塊通過其它幾點上的副本來對外提供服務。

          擴展性
                    擴展性是融合在CAP里面的特性。我覺得此處能夠單獨講一下。擴展性直接影響了分布式系統的好壞。系統開發初期不可能把系統的容量、峰值都考慮到。后期肯定牽扯到擴容,而怎樣做到快而不太影響業務的擴容策略。也是須要考慮的。(后面在介紹數據分布時會着重討論這個問題)



【分布式系統設計策略】


          1、重試機制


          普通情況下。寫一段網絡交互的代碼,發起rpc或者http,都會遇到請求超時而失敗情況。可能是網絡抖動(臨時的網絡變更導致包不可達,比方拓撲變更)或者對端掛掉。這時一般處理邏輯是將請求包在一個重試循環塊里,例如以下:

int retry = 3;
while(!request() && retry--)
    sched_yield();   // or usleep(100)

          此種模式能夠防止網絡臨時的抖動,一般停頓時間非常短,並重試多次后,請求成功!

但不能防止對端長時間不能連接(網絡問題或進程問題)

          2、心跳機制

          心跳顧名思義,就是以固定的頻率向其它節點匯報當前節點狀態的方式。

收到心跳。一般能夠覺得一個節點和如今的網絡拓撲是良好的。當然,心跳匯報時,一般也會攜帶一些附加的狀態、元數據信息,以便管理。例如以下圖:
 


          但心跳不是萬能的。收到心跳能夠確認ok,可是收不到心跳卻不能確認節點不存在或者掛掉了,由於可能是網絡原因倒是鏈路不通可是節點依然在工作。
          所以切記,”心跳“僅僅能告訴你正常的狀態是ok,它不能發現節點是否真的死亡。有可能還在繼續服務。(后面會介紹一種可靠的方式 -- Lease機制)


          3、副本


          副本指的是針對一份數據的多份冗余拷貝,在不同的節點上持久化同一份數據,當某一個節點的數據丟失時。能夠從副本上獲取數據。數據副本是分布式系統解決數據丟失異常的僅有的唯一途徑。當然對多份副本的寫入會帶來一致性和可用性的問題。比方規定副本數為3,同步寫3份。會帶來3次IO的性能問題。還是同步寫1份,然后異步寫2份。會帶來一致性問題,比方后面2份未寫成功其它模塊就去讀了(下個小結會詳細討論假設在副本一致性中間做取舍)。


          4、中心化/無中心化


          系統模型這方面,無非就是兩種:
          中心節點,比如mysql的MSS單主雙從、MongDB Master、HDFS NameNode、MapReduce JobTracker等,有1個或幾個節點充當整個系統的核心元數據及節點管理工作,其它節點都和中心節點交互。這樣的方式的優點顯而易見,數據和管理高度統一集中在一個地方,easy聚合。就像領導者一樣。其它人都服從就好。簡單可行。
          可是缺點是模塊高度集中,easy形成性能瓶頸,而且假設出現異常,就像群龍無首一樣。

          無中心化的設計。比如cassandra、zookeeper,系統中不存在一個領導者,節點彼此通信而且彼此合作完畢任務。優點在於假設出現異常。不會影響總體系統,局部不可用。缺點是比較協議復雜,而且須要各個節點間同步信息。


【分布式系統設計實踐】


          主要的理論和策略簡介這么多,后面本人會從project的角度,細化說一下”數據分布“、"副本控制"和"高可用協議"

          在分布式系統中,不管是計算還是存儲,處理的對象都是數據,數據不存在於一台機器或進程中。這就牽扯到怎樣多機均勻分發數據的問題,此小結主要討論"哈希取模",”一致性哈希“,”范圍表划分“,”數據塊划分“

          1、哈希取模:

                    哈希方式是最常見的數據分布方式。實現方式是通過能夠描寫敘述記錄的業務的id或key(比方用戶 id)。通過Hash函數的計算求余。

余數作為處理該數據的server索引編號處理。如圖:
                    

                    這樣的優點是僅僅須要通過計算就能夠映射出數據和處理節點的關系,不須要存儲映射。難點就是假設id分布不均勻可能出現計算、存儲傾斜的問題,在某個節點上分布過重。而且當處理節點宕機時。這樣的”硬哈希“的方式會直接導致部分數據異常,還有擴容非常困難,原來的映射關系全部發生變更。

                    此處。假設是”無狀態“型的節點,影響比較小,但遇到”有狀態“的存儲節點時,會發生大量數據位置須要變更。發生大量數據遷移的問題。

這個問題在實際生產中。能夠通過按2的冪的機器數,成倍擴容的方式來緩解,如圖:


                   

                    只是擴容的數量和方式后收到非常大限制。以下介紹一種”自適應“的方式解決擴容和容災的問題。


          2、一致性哈希:

          一致性哈希 -- Consistent Hash 是使用一個哈希函數計算數據或數據特征的哈希值。令該哈希函數的輸出值域為一個封閉的環。最大值+1=最小值。將節點隨機分布到這個環上,每一個節點負責處理從自己開始順
          時針至下一個節點的全部哈希值域上的數據,如圖:
          

################################################3

          一致性哈希的優點在於能夠任意動態加入、刪除節點,每次加入、刪除一個節點僅影響一致性哈希環上相鄰的節點。

為了盡可能均勻的分布節點和數據,一種常見的改進算法是引入虛節點的概念。系統會創建很多虛擬節點。個數遠大於當前節點的個數,均勻分布到一致性哈希值域環上。

讀寫數據時。首先通過數據的哈希值在環上找到相應的虛節點,然后查找到相應的real節點。這樣在擴容和容錯時,大量讀寫的壓力會再次被其它部分節點分攤,主要攻克了壓力集中的問題如圖:

 


 


          3、數據范圍划分:

          有些時候業務的數據id或key分布不是非常均勻,而且讀寫也會呈現聚集的方式。比方某些id的數據量特別大,這時候能夠將數據按Group划分。從業務角度划分比方id為0~10000,已知8000以上的id可能訪問量特別大,那么分布能夠划分為[[0~8000],[8000~9000],[9000~1000]]。將小訪問量的聚集在一起。
          這樣能夠依據真實場景按需划分。缺點是由於這些信息不能通過計算獲取,須要引入一個模塊存儲這些映射信息。

這就添加了模塊依賴,可能會有性能和可用性的額外代價。



          4、數據塊划分:


          很多文件系統經常採用相似設計,將數據按固定塊大小(比方HDFS的64MB)。將數據分為一個個大小固定的塊,然后這些塊均勻的分布在各個節點,這樣的做法也須要外部節點來存儲映射關系。
          由於與詳細的數據內容無關。按數據量分布數據的方式一般沒有數據傾斜的問題,數據總是被均勻切分並分布到集群中。當集群須要又一次負載均衡時。僅僅需通過遷移數據塊就可以完畢。



          如圖:
          


          大概說了一下數據分布的詳細實施,后面依據這些分布,看看project中各個節點間怎樣相互配合、管理。一起對外服務。




                    1、paxos

  paxos非常多人都聽說過了。這是唯一一個被認可的在project中證實的強一致性、高可用的去中心化分布式協議。
盡管論文里提到的概念比較復雜,但基本流程不難理解。本人能力有限。這里僅僅簡單的闡述一下基本原理:
Paxos 協議中,有三類角色: 
Proposer:Proposer 能夠有多個,Proposer 提出議案。此處定義為value。不同的 Proposer 能夠提出不同的甚至矛盾的 value。比如某個 Proposer 提議“將變量a設置為x1” 。還有一個 Proposer 提議“將變量a設置為x2” ,但對同一輪 Paxos過程,最多僅僅有一個 value 被批准。 
Acceptor: 批准者。

Acceptor 有 N 個。 Proposer 提出的 value 必須獲得超過半數(N/2+1)的 Acceptor批准后才干通過。Acceptor 之間對等獨立。

 
Learner:學習者。Learner 學習被批准的 value。所謂學習就是通過讀取各個 Proposer 對 value的選擇結果, 假設某個 value 被超過半數 Proposer 通過。 則 Learner 學習到了這個 value。從而學習者須要至少讀取 N/2+1 個 Accpetor。至多讀取 N 個 Acceptor 的結果后,能學習到一個通過的 value。


  paxos在開源界里比較好的實現就是zookeeper(相似Google chubby),zookeeper犧牲了分區容忍性。在一半節點宕機情況下,zookeeper就不可用了。

能夠提供中心化配置管理下發、分布式鎖、選主等消息隊列等功能。

當中前兩者依靠了Lease機制來實現節點存活感知和網絡異常檢測。

                    2、Lease機制

  Lease英文含義是”租期“、”承諾“。

在分布式環境中,此機制描寫敘述為:
  Lease 是由授權者授予的在一段時間內的承諾。授權者一旦發出 lease,則不管接受方是否收到。也不管興許接收方處於何種狀態,僅僅要 lease 只是期,授權者一定遵守承諾,按承諾的時間、內容運行。

接收方在有效期內能夠使用頒發者的承諾。僅僅要 lease 過期,接
收方放棄授權。不再繼續運行,要又一次申請Lease。
                             如圖:

                              

                             


  Lease使用方法舉例1:
  現有一個相似DNS服務的系統,數據的規律是修改非常少,大量的讀操作。

client從服務端獲取數據,假設每次都去server查詢,則量比較大。

能夠把數據緩存在本地,當數據有變動的時候又一次拉取。如今server以lease的形式,把數據和lease一同推送給client,在lease中存放承諾該數據的不變的時間,然后client就能夠一直放心的使用這些數據(由於這些數據在server不會發生變更)。假設有client修改了數據,則把這些數據推送給server。server會堵塞一直到已公布的全部lease都已經超時用完,然后后面發送數據和lease時。更新如今的數據

  這里有個優化能夠做。當server收到數據更新須要等全部已經下發的lease超時的這段時間,能夠直接發送讓數據和lease失效的指令到client。減小server等待時間,假設不是全部的lease都失效成功。則退化為前面的等待方案(概率小)。





  Lease使用方法舉例2:

  現有一個系統。有三個角色。選主模塊Manager。唯一的Master,和其它salver節點。

slaver都向Maganer注冊自己,並由manager選出唯一的Master節點並告知其它slaver節點。當網絡出現異常時,可能是Master和Manager之間的鏈路斷了,Master覺得Master已經死掉了。則會再選出一個Master,可是原來的Master對其它網絡鏈路可能都還是正常的,原來的Master覺得自己還是主節點,繼續服務。這時候系統中就出現了”雙主“。俗稱”腦裂“。


  解決問題的方式能夠通過Lease,來規定節點能夠當Master的時間。假設沒有可用的Lease。則自己主動退化為Slaver。

假設出現”雙主“,原Master會由於Lease到期而放棄當Master,退化為Slaver,恢復了一個Master的情況。


                    3、選主算法

                    有些時候出於系統某些特性,能夠在有取舍的情況下,實現一些相似Lease的選主的方案,可見本人還有一篇文章:

  http://blog.csdn.net/gugemichael/article/details/8964834









免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM