分佈式系統幾種典型一致性算法簡述

在分佈式系統中,咱們常常遇到多數據副本保持一致的問題,在咱們所能找到的資料中該問題講的很籠統,模模糊糊的,把多個問題或分類糅合在一塊兒,難以理解。在思考和翻閱資料後,通俗地把一致性的問題可分解爲2個問題:

一、任何一次修改保證數據一致性。 php

二、屢次數據修改的一致性。 node

在弱一致性的算法,不要求每次修改的內容在修改後多副本的內容是一致的,對問題1的解決比較寬鬆,更多解決問題2,該類算法追求每次修改的高度併發性,減小多副本之間修改的關聯性,以得到更好的併發性能。例如最終一致性,無所謂每次用戶修改後的多副本的一致性及格過,只要求在單調的時間方向上,數據最終保持一致,如此得到了修改極大的併發性能。 算法

在強一致性的算法中,強調單次修改後結果的一致,須要保證了對問題1和問題2要求的實現,犧牲了併發性能。本文是討論對解決問題1實現算法,這些算法每每在強一致性要求的應用中使用。 數據庫

解決問題1的方法,一般有兩階段提交算法、採用分佈式鎖服務和採用樂觀鎖原理實現的同步方式,下面分別介紹這幾種算法的實現原理。 網絡

  • 兩階段提交算法


在兩階段提交協議中,系統通常包含兩類機器(或節點):一類爲協調者(coordinator),一般一個系統中只有一個;另外一類爲事務參與者(participants,cohorts或workers),通常包含多個,在數據存儲系統中能夠理解爲數據副本的個數。兩階段提交協議由兩個階段組成,在正常的執行下,這兩個階段的執行過程以下所述: 併發

  • 階段1:請求階段(commit-request phase,或稱表決階段,voting phase)。

         在請求階段,協調者將通知事務參與者準備提交或取消事務,而後進入表決過程。在表決過程當中,參與者將告知協調者本身的決策:贊成(事務參與者本地做業執行成功)或取消(本地做業執行故障)。 分佈式

  • 階段2:提交階段(commit phase)。

        在該階段,協調者將基於第一個階段的投票結果進行決策:提交或取消。當且僅當全部的參與者贊成提交事務協調者才通知全部的參與者提交事務,不然協調者將通知全部的參與者取消事務。參與者在接收到協調者發來的消息後將執行響應的操做。 性能

          舉個例子:A組織B、C和D三我的去爬長城:若是全部人都贊成去爬長城,那麼活動將舉行;若是有一人不一樣意去爬長城,那麼活動將取消。用2PC算法解決該問題的過程以下:
  • 首先A將成爲該活動的協調者,B、C和D將成爲該活動的參與者。
  • 階段1:A發郵件給B、C和D,提出下週三去登山,問是否贊成。那麼此時A須要等待B、C和D的郵件。B、C和D分別查看本身的日程安排表。B、C發現本身在當日沒有活動安排,則發郵件告訴A它們贊成下週三去爬長城。因爲某種緣由,D白天沒有查看郵件。那麼此時A、B和C均須要等待。到晚上的時候,D發現了A的郵件,而後查看日程安排,發現週三當天已經有別的安排,那麼D回覆A說活動取消吧。
  • 階段2:此時A收到了全部活動參與者的郵件,而且A發現D下週三不能去登山。那麼A將發郵件通知B、C和D,下週三爬長城活動取消。此時B、C回覆A「太惋惜了」,D回覆A「很差意思」。至此該事務終止。

兩階段提交算法在分佈式系統結合,可實現單用戶對文件(對象)多個副本的修改,多副本數據的同步。其結合的原理以下: atom

          一、客戶端(協調者)向全部的數據副本的存儲主機(參與者)發送:修改具體的文件名、偏移量、數據和長度信息,請求修改數據,該消息是1階段的請求消息。 spa

二、存儲主機接收到請求後,備份修改前的數據以備回滾,修改文件數據後,向客戶端迴應修改爲功的消息。 若是存儲主機因爲某些緣由(磁盤損壞、空間不足等)不能修改數據,迴應修改失敗的消息。

三、客戶端接收發送出去的每個消息迴應,若是存儲主機所有迴應都修改爲功,向每存儲主機發送確認修改的提交消息;若是存在存儲主機迴應修改失敗,或者超時未迴應,客戶端向全部存儲主機發送取消修改的提交消息。該消息是2階段的提交消息。

四、存儲主機接收到客戶端的提交消息,若是是確認修改,則直接回應該提交OK消息;若是是取消修改,則將修改數據還原爲修改前,而後迴應取消修改OK的消息。

五、 客戶端接收所有存儲主機的迴應,整個操做成功。

      在該過程當中可能存在通訊失敗,例如網絡中斷、主機宕機等諸多的緣由,對於未在算法中定義的其它異常,都認爲是提交失敗,都須要回滾,這是該算法基於肯定的通訊回覆實現的,在參與者的肯定回覆(不管是回覆失敗仍是回覆成功)之上執行邏輯處理,符合肯定性的條件固然可以得到肯定性的結果哲學原理。

  • 分佈式鎖服務

 分佈式鎖是對數據被外界修改持保守態度,在整個數據處理過程當中將數據處於鎖定狀態,在用戶修改數據的同時,其它用戶不容許修改。
      採用分佈式鎖服務實現數據一致性,是在操做目標以前先獲取操做許可,而後再執行操做,若是其餘用戶同時嘗試操做該目標將被阻止,直到前一個用戶釋放許可後,其餘用戶纔可以操做目標。分析這個過程,若是隻有一個用戶操做目標,沒有多個用戶併發衝突,也申請了操做許可,形成了因爲申請操做許可所帶來的資源使用消耗,浪費網絡通訊和增長了延時。
      採用分佈式鎖實現多副本內容修改的一致性問題, 選擇控制內容顆粒度實現申請鎖服務。例如咱們要保證一個文件的多個副本修改一致, 能夠對整個文件修改設置一把鎖,修改時申請鎖,修改這個文件的多個副本,確保多個副本修改的一致,修改完成後釋放鎖;也能夠對文件分段,或者是文件中的單個字節設置鎖, 實現更細顆粒度的鎖操做,減小衝突。

經常使用的鎖實現算法有Lamport bakery algorithm (俗稱麪包店算法), 還有Paxos算法。下面對其原理作簡單概述。


  • Lamport麪包店算法


是解決多個線程併發訪問一個共享的單用戶資源的互斥問題的算法。 由Leslie Lamport英語Leslie Lamport)發明。    
    Lamport把這個併發控制算法能夠很是直觀地類比爲顧客去麪包店採購。麪包店只能接待一位顧客的採購。已知有n位顧客要進入麪包店採購,安排他們按照次序在前臺登記一個簽到號碼。該簽到號碼逐次加1。根據簽到號碼的由小到大的順序依次入店購貨。完成購買的顧客在前臺把其簽到號碼歸0. 若是完成購買的顧客要再次進店購買,就必須從新排隊。

     這個類比中的顧客就至關於線程,而入店購貨就是進入臨界區獨佔訪問該共享資源。因爲計算機實現的特色,存在兩個線程得到相同的簽到號碼的狀況,這是由於兩個線程幾乎同時申請排隊的簽到號碼,讀取已經發出去的簽到號碼狀況,這兩個線程讀到的數據是徹底同樣的,而後各自在讀到的數據上找到最大值,再加1做爲本身的排隊簽到號碼。爲此,該算法規定若是兩個線程的排隊簽到號碼相等,則線程id號較小的具備優先權。

把該算法原理與分佈式系統相結合,便可實現分步鎖。


  • Paxos算法


 該算法比較熱門,參見WIKI,http://zh.wikipedia.org/wiki/Paxos%E7%AE%97%E6%B3%95

Paxos算法解決的問題是一個分佈式系統如何就某個值(決議)達成一致。一個典型的場景是,在一個分佈式數據庫系統中,若是各節點的初始狀態一致,每一個節點都執行相同的操做序列,那麼他們最後能獲得一個一致的狀態。爲保證每一個節點執行相同的命令序列,須要在每一條指令上執行一個「一致性算法」以保證每一個節點看到的指令一致。一個通用的一致性算法能夠應用在許多場景中,是分佈式計算中的重要問題。節點通訊存在兩種模型:共享內存(Shared memory)和消息傳遞(Messages passing)。Paxos算法就是一種基於消息傳遞模型的一致性算法。BigTable使用一個分佈式數據鎖服務Chubby,而Chubby使用Paxos算法來保證備份的一致性。

  • 採用樂觀鎖原理實現的同步

      咱們舉個例子說明該算法的實現原理。如一個金融系統,當某個操做員讀取用戶的數據,並在讀出的用戶數據的基礎上進行修改時(如更改用戶賬戶餘額),若是採用前面的分佈式鎖服務機制,也就意味着整個操做過程當中(從操做員讀出數據、開始修改直至提交修改結果的全過程,甚至還包括操做員中途去煮咖啡的時間),數據庫記錄始終處於加鎖狀態,能夠想見,若是面對幾百上千個併發,這樣的狀況將致使怎樣的後果。
      樂觀鎖機制在必定程度上解決了這個問題。樂觀鎖,大可能是基於數據版本( Version)記錄機制實現。何謂數據版本?即爲數據增長一個版本標識,在基於數據庫表的版本解決方案中,通常是經過爲數據庫表增長一個 「version」 字段來實現。讀取出數據時,將此版本號一同讀出,以後更新時,對此版本號加一。此時,將提交數據的版本數據與數據庫表對應記錄的當前版本信息進行比對,若是提交的數據版本號大於數據庫表當前版本號,則予以更新,不然認爲是過時數據。
      
 對於上面修改用戶賬戶信息的例子而言,假設數據庫中賬戶信息表中有一個 version 字段,當前值爲 1 ;而當前賬戶餘額字段( balance )爲 $100 。

  1. 操做員 A 此時將其讀出(version=1 ),並從其賬戶餘額中扣除 $50($100-$50 )。
  2. 在操做員 A 操做的過程當中,操做員B也讀入此用戶信息( version=1 ),並從其賬戶餘額中扣除 $20 ( $100-$20 )。
  3. 操做員 A 完成了修改工做,將數據版本號加一( version=2 ),連同賬戶扣除後餘額( balance=$50 ),提交至數據庫更新,此時因爲提交數據版本大於數據庫記錄當前版本,數據被更新,數據庫記錄 version 更新爲 2 。
  4. 操做員 B 完成了操做,也將版本號加一( version=2 )試圖向數據庫提交數據( balance=$80 ),但此時比對數據庫記錄版本時發現,操做員 B 提交的數據版本號爲 2 ,數據庫記錄當前版本也爲 2 ,不知足 「 提交版本必須大於記錄當前版本才能執行更新 「 的樂觀鎖策略,所以,操做員 B 的提交被駁回。這樣,就避免了操做員 B 用基於 version=1 的舊數據修改的結果覆蓋操做員A 的操做結果的可能。

樂觀鎖機制與分佈式系統相結合上, 我整理了僞代碼以下:

obj  操做的目標
vlaue 修改的值
atom_update_ver 每一個目標上的版本,每次修改該值遞增
set( obj, value)
{
     //從每一個節點上取出修改前的對象版本
    get original_ver = obj.atom_update_ver from each node;
     //將值賦到每一個節點的obj目標
    set obj = value from each node;
     //條件修改每一個節點的obj版本,目標版本加一
     //比較和修改操做是原子操做
    result = (set obj.atom_update_ver = original_ver + 1
                  where  original_ver + 1 >  obj.atom_update_ver
                   for each node);
    if(result == ok)
        return set_ok;
    else
        return set(obj, value);//不成功遞歸修改

該算法未考慮節點下線、失效等問題,在後續我將分析採用樂觀鎖原理實現一致性算法,解決問題二、節點失效、通訊失敗等問題。

相關文章
相關標籤/搜索