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

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

 

 

【分佈式系統中的概念】

        三元組    

                其實,分佈式系統說白了,就是不少機器組成的集羣,靠彼此之間的網絡通訊,擔當的角色可能不一樣,共同完成同一個事情的系統。若是按」實體「來劃分的話,就是以下這幾種:
                一、節點 -- 系統中按照協議完成計算工做的一個邏輯實體,多是執行某些工做的進程或機器
                二、網絡 -- 系統的數據傳輸通道,用來彼此通訊。通訊是具備方向性的。
                三、存儲 -- 系統中持久化數據的數據庫或者文件存儲。

                如圖
                

node

        狀態特性

                各個節點的狀態能夠是「無狀態」或者「有狀態的」.mysql

                通常認爲,節點是偏計算和通訊的模塊,通常是無狀態的。這類應用通常不會存儲本身的中間狀態信息,好比Nginx,通常狀況下是轉發請求而已,不會存儲中間信息。另外一種「有狀態」的,如MySQL數據庫,狀態和數據所有持久化到磁盤等介質。linux

                「無狀態」的節點通常咱們認爲是可隨意重啓的,由於重啓後只須要馬上工做就好。「有狀態」的則不一樣,須要先讀取持久化的數據,才能開始服務。因此,「無狀態」的節點通常是能夠隨意擴展的,「有狀態」的節點須要一些控制協議來保證擴展。

web

        系統異常

                異常,可認爲是節點由於某種緣由不能工做,此爲節點異常。還有由於網絡緣由,臨時、永久不能被其餘節點所訪問,此爲網絡異常。在分佈式系統中,要有對異常的處理,保證集羣的正常工做。


算法

【分佈式系統與單節點的不一樣】

 

          一、從linux write()系統調用提及

         衆所周知,在unix/linux/mac(類Unix)環境下,兩個機器通訊,最經常使用的就是經過socket鏈接對方。傳輸數據的話,無非就是調用write()這個系統調用,把一段內存緩衝區發出去。可是能夠進一步想一下,write()以後能確認對方收到了這些數據嗎?

         答案確定是不能,緣由就是發送數據須要走內核->網卡->鏈路->對端網卡->內核,這一路徑太長了,因此只能是異步操做。write()把數據寫入內核緩衝區以後就返回到應用層了,具體後面什麼時候發送、怎麼發送、TCP怎麼作滑動窗口、流控都是tcp/ip協議棧內核的事情了。

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

sql

         二、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受到了它的成功的消息。


服務器

         三、不可控的狀態


         在單系統編程中,咱們對系統狀態是很是可控的。好比函數調用、邏輯運算,要麼成功,要麼失敗,由於這些操做被框在一個機器內部,cpu/總線/內存都是能夠快速獲得反饋的。開發者能夠針對這兩個狀態很明確的作出程序上的判斷和後續的操做。
         而在分佈式的網絡環境下,這就變得微妙了。好比一次rpc、http調用,可能成功、失敗,還有多是「超時」,這就比前者的狀態多了一個不可控因素,致使後面的代碼不是很容易作出判斷。試想一下,用A用支付寶向B轉了一大筆錢,當他按下「確認」後,界面上有個圈在轉啊轉,而後顯示請求超時了,而後A就抓狂了,不知道到底錢轉沒轉過去,開始確認本身的帳戶、確認B的帳戶、打電話找客服等等。

         因此分佈式環境下,咱們的其實要時時刻刻考慮面對這種不可控的「第三狀態」設計開發,這也是挑戰之一。

         四、視」異常「爲」正常「

         單系統下,進程/機器的異常機率十分小。即便出現了問題,能夠經過人工干預重啓、遷移等手段恢復。但在分佈式環境下,機器上千臺,每幾分鐘均可能出現宕機、死機、網絡斷網等異常,出現的機率很大。因此,這種環境下,進程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。本人以本身的觀點總結了一下:

          一致性
                    描述當前全部節點存儲數據的統一模型,分爲強一致性和弱一致性:
                    強一致性描述了全部節點的數據高度一致,不管從哪一個節點讀取,都是同樣的。無需擔憂同一時刻會得到不一樣的數據。是級別最高的,實現的代價比較高
                    如圖:
    
                              弱一致性又分爲單調一致性和最終一致性:
                              一、單調一致性強調數據是按照時間的新舊,單調向最新的數據靠近,不會回退,如:
                                   數據存在三個版本v1->v2->v3,獲取只能向v3靠近(如取到的是v2,就不可能再次得到v1)
                              二、最終一致性強調數據通過一個時間窗口以後,只要多嘗試幾回,最終的狀態是一致的,是最新的數據
                                    如圖:



                              強一致性的場景,就好像交易系統,存取錢的+/-操做必須是立刻一致的,不然會令不少人誤解。
                              弱一致性的場景,大部分就像web互聯網的模式,好比發了一條微博,改了某些配置,可能不會立刻生效,但刷新幾回後就能夠看到了,其實弱一致性就是在系統上經過業務可接受的方式換取了一些系統的低複雜度和可用性。


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


         分區容忍性
                    在系統某些節點或網絡有異常的狀況下,系統依舊能夠繼續服務。
                    這一般是有負載均衡和副原本支撐的。例如計算模塊異常可經過負載均衡引流到其餘平行節點,存儲模塊經過其餘幾點上的副原本對外提供服務。

          擴展性
                    擴展性是融合在CAP裏面的特性,我以爲此處能夠單獨講一下。擴展性直接影響了分佈式系統的好壞,系統開發初期不可能把系統的容量、峯值都考慮到,後期確定牽扯到擴容,而如何作到快而不太影響業務的擴容策略,也是須要考慮的。(後面在介紹數據分佈時會着重討論這個問題)



【分佈式系統設計策略】

 

          一、重試機制

 

          通常狀況下,寫一段網絡交互的代碼,發起rpc或者http,都會遇到請求超時而失敗狀況。多是網絡抖動(暫時的網絡變動致使包不可達,好比拓撲變動)或者對端掛掉。這時通常處理邏輯是將請求包在一個重試循環塊裏,以下:

 

[cpp]  view plain copy
 print?
  1. int retry = 3;  
  2. while(!request() && retry--)  
  3.     sched_yield();   // or usleep(100)  

 

 

          此種模式能夠防止網絡暫時的抖動,通常停頓時間很短,並重試屢次後,請求成功!但不能防止對端長時間不能鏈接(網絡問題或進程問題)

          二、心跳機制

          心跳顧名思義,就是以固定的頻率向其餘節點彙報當前節點狀態的方式。收到心跳,通常能夠認爲一個節點和如今的網絡拓撲是良好的。固然,心跳彙報時,通常也會攜帶一些附加的狀態、元數據信息,以便管理。以下圖:
 


          但心跳不是萬能的,收到心跳能夠確認ok,可是收不到心跳卻不能確認節點不存在或者掛掉了,由於多是網絡緣由卻是鏈路不通可是節點依舊在工做。
          因此切記,」心跳「只能告訴你正常的狀態是ok,它不能發現節點是否真的死亡,有可能還在繼續服務。(後面會介紹一種可靠的方式 -- Lease機制)


          三、副本


          副本指的是針對一份數據的多份冗餘拷貝,在不一樣的節點上持久化同一份數據,當某一個節點的數據丟失時,能夠從副本上獲取數據。數據副本是分佈式系統解決數據丟失異常的僅有的惟一途徑。固然對多份副本的寫入會帶來一致性和可用性的問題,好比規定副本數爲3,同步寫3份,會帶來3次IO的性能問題。仍是同步寫1份,而後異步寫2份,會帶來一致性問題,好比後面2份未寫成功其餘模塊就去讀了(下個小結會詳細討論若是在副本一致性中間作取捨)。


          四、中心化/無中心化


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

 

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

 

【分佈式系統設計實踐】


          基本的理論和策略簡單介紹這麼多,後面本人會從工程的角度,細化說一下」數據分佈「、"副本控制"和"高可用協議"

          在分佈式系統中,不管是計算仍是存儲,處理的對象都是數據,數據不存在於一臺機器或進程中,這就牽扯到如何多機均勻分發數據的問題,此小結主要討論"哈希取模",」一致性哈希「,」範圍表劃分「,」數據塊劃分「

          一、哈希取模:

                    哈希方式是最多見的數據分佈方式,實現方式是經過能夠描述記錄的業務的id或key(好比用戶 id),經過Hash函數的計算求餘。餘數做爲處理該數據的服務器索引編號處理。如圖:
                    

                    這樣的好處是隻須要經過計算就能夠映射出數據和處理節點的關係,不須要存儲映射。難點就是若是id分佈不均勻可能出現計算、存儲傾斜的問題,在某個節點上分佈太重。而且當處理節點宕機時,這種」硬哈希「的方式會直接致使部分數據異常,還有擴容很是困難,原來的映射關係所有發生變動。

                    此處,若是是」無狀態「型的節點,影響比較小,但遇到」有狀態「的存儲節點時,會發生大量數據位置須要變動,發生大量數據遷移的問題。這個問題在實際生產中,能夠經過按2的冪的機器數,成倍擴容的方式來緩解,如圖:

 

                   

                    不過擴容的數量和方式後收到很大限制。下面介紹一種」自適應「的方式解決擴容和容災的問題。


          二、一致性哈希:

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

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

          一致性哈希的優勢在於能夠任意動態添加、刪除節點,每次添加、刪除一個節點僅影響一致性哈希環上相鄰的節點。 爲了儘量均勻的分佈節點和數據,一種常見的改進算法是引入虛節點的概念,系統會建立許多虛擬節點,個數遠大於當前節點的個數,均勻分佈到一致性哈希值域環上。讀寫數據時,首先經過數據的哈希值在環上找到對應的虛節點,而後查找到對應的real節點。這樣在擴容和容錯時,大量讀寫的壓力會再次被其餘部分節點分攤,主要解決了壓力集中的問題。如圖:

 


 


          三、數據範圍劃分:

          有些時候業務的數據id或key分佈不是很均勻,而且讀寫也會呈現彙集的方式。好比某些id的數據量特別大,這時候能夠將數據按Group劃分,從業務角度劃分好比id爲0~10000,已知8000以上的id可能訪問量特別大,那麼分佈能夠劃分爲[[0~8000],[8000~9000],[9000~1000]]。將小訪問量的彙集在一塊兒。
          這樣能夠根據真實場景按需劃分,缺點是因爲這些信息不能經過計算獲取,須要引入一個模塊存儲這些映射信息。這就增長了模塊依賴,可能會有性能和可用性的額外代價。

          四、數據塊劃分:


          許多文件系統常常採用相似設計,將數據按固定塊大小(好比HDFS的64MB),將數據分爲一個個大小固定的塊,而後這些塊均勻的分佈在各個節點,這種作法也須要外部節點來存儲映射關係。
          因爲與具體的數據內容無關,按數據量分佈數據的方式通常沒有數據傾斜的問題,數據老是被均勻切分並分佈到集羣中。當集羣須要從新負載均衡時,只需經過遷移數據塊便可完成。

          如圖:
          


          大概說了一下數據分佈的具體實施,後面根據這些分佈,看看工程中各個節點間如何相互配合、管理,一塊兒對外服務。


                    一、paxos

  paxos不少人都據說過了,這是惟一一個被承認的在工程中證明的強一致性、高可用的去中心化分佈式協議。
雖然論文裏提到的概念比較複雜,但基本流程不難理解。本人能力有限,這裏只簡單的闡述一下基本原理:
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機制來實現節點存活感知和網絡異常檢測。

                    二、Lease機制

  Lease英文含義是」租期「、」承諾「。在分佈式環境中,此機制描述爲:
  Lease 是由受權者授予的在一段時間內的承諾。受權者一旦發出 lease,則不管接受方是否收到,也不管後續接收方處於何種狀態,只要 lease 不過時,受權者必定遵照承諾,按承諾的時間、內容執行。接收方在有效期內可使用頒發者的承諾,只要 lease 過時,接
收方放棄受權,再也不繼續執行,要從新申請Lease。
                             如圖:

                              

                             


  Lease用法舉例1:
  現有一個相似DNS服務的系統,數據的規律是改動不多,大量的讀操做。客戶端從服務端獲取數據,若是每次都去服務器查詢,則量比較大。能夠把數據緩存在本地,當數據有變更的時候從新拉取。如今服務器以lease的形式,把數據和lease一同推送給客戶端,在lease中存放承諾該數據的不變的時間,而後客戶端就能夠一直放心的使用這些數據(由於這些數據在服務器不會發生變動)。若是有客戶端修改了數據,則把這些數據推送給服務器,服務器會阻塞一直到已發佈的全部lease都已經超時用完,而後後面發送數據和lease時,更新如今的數據。

  這裏有個優化能夠作,當服務器收到數據更新須要等全部已經下發的lease超時的這段時間,能夠直接發送讓數據和lease失效的指令到客戶端,減少服務器等待時間,若是不是全部的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的狀況。


                    三、選主算法

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

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

相關文章
相關標籤/搜索