區塊鏈的底層原理研究(1)分布式系統 Paxos協議解析


分布式系統研究簡介:

隨着計算及技術的發展,如今幾乎所有實際應用的計算機系統都是分布式的。分布式系統的理論研究其實是從Leslie Lamport在1989年提出關於Paxos算法的論文開始的。這篇論文企圖用一個寓言的方式來描述Paxos算法;但由於其理論過於超前,闡述方式非常奇特,導致其很長一段時間不被學術界接受和認可。從1996年開始,學術界才開始大量的分布式計算的理論研究,2001年Lampson發表的論文“The ABCD's of Paxos"對當時的Paxos理論進行了分類和集中地描述,奠定了分布式系統的理論基礎。緊接着產業界,主要是互聯網公司對大規模分布式計算機系統的需求和應用,業界對這些基礎理論的應用和實踐進一步推動了其發展,理論和實踐的結合,催生出了大量的成果。Google公司由於自身的需要,在這個過程中扮演了尤其重要的角色。現在,存儲,計算相關的分布式系統很大程度上都是Google應用和研究成果的使用和衍生,比如BigTable,MapReduce,Chubby/Zookeeper(Yahoo)等等。

區塊鏈的兩個理論基石是分布式系統和加密和密碼學,在這篇文章里,會重點討論分布式系統相關的內容。分布式系統能夠比較容易得橫向擴大存儲容量,計算能力以及連接不同的地理區域。但遇到的最大問題就是系統協同性問題(Coordination)。在不同的系統中有不同的體現。區塊鏈中主要是一致性,共識,賬單,協議... 在其他的系統中容錯性,可恢復性和可用性也是常見的問題。

Paxos算法:
1. 分布式系統的構成:

系統中的一個工作單元通常被稱作一個節點(Node),節點通過通訊網絡連接起來,節點之間通過消息機制傳遞信息和命令。傳統的網絡服務分為服務器和客戶端兩種節點,P2P網絡中每個計算機都是一個平等的節點(同時有服務器和客戶端的功能)。區塊鏈網絡通常都是由P2P節點組成的,這其實和其去中心化的需求相關,區塊鏈網絡中不能夠也不應該存在一個或幾個集中計算和存儲資源的中心服務器/集群(DPOS的區塊鏈系統【如EOS】其實並不符合區塊鏈技術去中心化的基本原則,我覺得應該找到一個更合理的解決方案,這個會在后面的文章討論)。

2. 消息傳遞:

分布式系統中的節點之間必須能夠互相通訊,通訊是通過消息傳遞的形式完成的。作為客戶端的節點向服務端節點發送請求消息;作為服務端的節點接受到請求消息,並處理消息。消息傳遞機制貌似比較簡單,但實際上衍生出很多需要解決的問題:

  • 消息損壞和丟失:對於分布式系統,一個重要假設就是消息傳輸的通道不是100%可靠的。那么在消息傳遞的過程中就會出現兩種情況,第一種是消息損壞,也就是送達服務器節點的消息和原始消息不同,這種情況比較容易處理,只要在消息包中加入校驗碼就可以確定消息是否損壞。第二種情況是消息丟失,沒有送達服務器節點,這種情況比較難以處理,通常的做法是采用帶確認的消息機制,服務端收到消息后會發給客戶節點一個確認消息,如果客戶節點沒有在一個合理時間收到確認消息,則會從新發送這條消息。帶確認的消息機制是很多底層網絡協議的基礎,比如TCP協議。
  • 消息延遲:在實際的系統中,每條消息傳遞所需的時間是不同的,即使是相同節點之間的消息傳遞也會有不同的延遲時間。如果存在多個客戶端同時發送命令,那到達服務器的順序是不可知的。如果這些消息(命令)的執行是有順序的,那就會導致不一致情況的出現,比如c1發送x=x+5命令,c2發出x=x*2的命令,這兩條命令執行順序不同,結果會完全不同。如果按到達先后的順序執行命令,則顯然是不可靠的。對於這種情況的處理,比較簡單得方式是通過一個串行化器(Serializer)接受命令,所有客戶端的命令發送給串行化器,串行化器將命令排序后提交服務器執行,完成后將執行結果返回所有客戶端。這種串行化器方式也被稱為主從復制模式(Master-Slave Replication)。這種方式解決了不一致問題,但是卻引發了串行化器的單點故障問題。
  • 節點故障:上面已經介紹了一種節點故障,叫做單點故障。除了單點故障以外,其實每一個節點都是可能出現故障,無法正常運行的,對於大量節點的互聯網應用,哪怕每個節點只有極小概率出現故障的情況下,有大量節點組成的整個系統仍然有非常大的概率出現故障節點。分布式系統必須能夠容忍一定比例的節點故障。
3. 兩階段協議和Paxos:

通過上面的描述,我們其實看到對於分布式系統來講,主要需要解決兩個問題。一,消息傳輸通道的不可靠性;二,節點或單點故障造成的問題。提出問題,下一步就是對問題的解決:

1. 采用兩階段協議的分布式系統:

前面講到對於命令執行的不一致性,我們引入了一種串行化器的解決方案(主從模式),對於一個小規模的分布式系統,通過采用高可靠性的設備和高性能災備系統,這個方案應該已經足夠勝任了。但是對於一個大規模的分布式系統,這個單點的串行化器無論從可靠性,性能還是安全性的角度來看都是一個極大的風險。兩階段協議將這個問題換了一個解決思路,把發送命令的權利下放給客戶端節點,但是客戶端節點必須獲得所有服務器的鎖的情況下才能夠發命令,一旦服務器執行命令完畢,客戶端收到確認通知則立即釋放鎖,其他的客戶端可以獲取鎖並發送命令。這樣保證在同一時間只有一個客戶端節點能夠給服務器發送命令:階段一:客戶端獲取服務端的鎖,第二階段:客戶端向服務端發送命令並執行。

兩階段協議避免使用串行化器解決了不一致問題。數據庫事務事實上也是一個兩階段協議的實現,第一個階段是准備(preparation)第二個階段是提交(commit),但是數據庫提交階段通常還是在數據庫服務器上進行的,這是兩階段協議的一種變化(PS:任何理論在實際的應用中都是會根據具體問題和情況變化的,理論的意義在於提供思路和方向,而不是照搬照抄,在工作中遇到兩種錯誤的態度1. 只是注意實際的系統,實際的問題,從不或者很少關注和提煉理論上的東西,這叫只修招式,不練內力,只能是花拳綉腳,知其然,不知其所以然。2. 重視標准和理論,任何系統的實現都一定要遵從現有論文上的理論模式或者標准中的實現,這叫不知變通,認死理做死工,更不可取)。

兩階段協議是否解決了單點故障的問題呢?我覺得沒有完全解決,需要進一步的改進才可以。兩階段協議本身只是提供服務器鎖的機制,但是並沒有提供如何判斷一個命令是否能夠執行的標准。如果有多個服務器的情況下(在區塊鏈網絡中這個非常常見,一個節點如果產生新的區塊,一定會通知其他所有連接的節點更新主鏈),是否需要所有服務器的鎖才能夠提交命令,還是只要獲得半數以上的服務器鎖就可以提交?如何處理和其他客戶端發出鎖請求的沖突?如果某些客戶端在釋放鎖之前就宕機如何處理?可以看到兩階段協議提出了一個不錯的思路,但是同時也引入了更多的問題。

2. Paxos

Paxos 引入了票(Ticket)的機制,實際上是對鎖機制的一種弱化,服務器隨時會可以生成一張新的票,客戶端可以請求並得到最新的票,前面產生的票沒有過期,仍然可以生成新的票。一旦接收到客戶端給服務端發送帶有票號的消息后,該票號過期。Paxos協議能夠更好得應對分布式系統中的節點故障,最簡單的Paxos協議分成三個階段執行。

  • 第一階段:客戶端向所有服務端請求一張票。
  • 第二階段:if 客戶端收到過半數服務器發回的票,then 客戶端將命令和票號同時發回服務器,服務器一旦收到票,檢查是否有效,如果有效則存儲命令並反饋success消息給客戶端。
  • 第三階段:if 客戶端收到過半數服務器的success反饋,then 客戶端提交給所有服務器執行命令的指令。指令完成,獲得結果,返回第一階段。

這個朴素(簡單)的Paxos協議是通過服務端來產生票,客戶端請求票。這個機制的一個問題是,在獲得半數以上的客戶端c1寫入命令u1后,另外一個客戶端c2可能獲得更新的票號,並給某些服務器寫入了新的命令u2. 於是執行階段某些服務器會執行c2。這個問題可以通過服務器向c2發送票號的同時發送命令u1來解決。如果u1命令已經獲得半數以上服務器的票,c2就會轉而支持c1。但是在沒有任何客戶端獲得半數票的情況下,所有的客戶端都會競爭指令的執行權。問題是如果客戶端c2從不同的服務器得到不止u1一個命令,客戶端應該選擇支持哪一個命令呢?同時還有個問題是服務器都是獨立維護自己的票號,這樣造成無法產生全局的唯一票號,造成的結果就是不知道哪個是最新產生的命令。通常對於分布是系統而言,支持最新的產生的命令是最安全的做法。於是引出了修改的Paxos協議。

修改版Paxos協議:

這個協議中由客戶端生成當前票號,這樣能夠保證最新票號的唯一性。

  • 初始化:客戶端存儲命令c,生成當前票號t+1。服務端存儲當前記錄的最大票號T, 當前存儲的命令C以及該命令的票號Tc
  • 第一階段:客戶端向所有服務端發送消息,獲的當前t=t+1的票號。服務端收到請求,如果t>T, 則T=t,返回ok,同時返回當前存儲的命令+票號Tc。
  • 第二階段:收到半數服務器回復 ok。如果最大票號Tc-max != 0,選擇支持Tc-max所對應的指令C; 如果Tc-max =0,說明當前系統中沒有存儲待執行的命令,C寫入原來准備提交的命令c。回復所有返回ok的服務器,提議(Tc-max,C)。服務器端,如果t=T, 則更新Tc = T。返回客戶端success。
  • 第三階段:如果過半數服務器回復success,則向所有服務端發送執行指令。 

Paxos協議確保了所有節點總是支持票號最大的命令(提議),以達成一致性,同時也可以很好地應對節點故障。無論客戶端還是服務端故障,都不會使系統失效。如果一個客戶端在執行階段前宕機,仍然可以保證最新的過半數命令得到執行,即使這個指令最早是宕機客戶端最早提交的。同時只要不是半數以上服務端故障,整個分布式系統仍然能夠正常運行。



注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2020 ITdaan.com