轉載於:https://www.cnblogs.com/mafeng/p/8405375.html
摘要部分
這篇論文描述了一種副本復制(replication)算法解決拜占庭容錯問題。作者認為拜占庭容錯算法將會變得更加重要,因為惡意攻擊和軟件錯誤的發生將會越來越多,並且導致失效的節點產生任意行為。(拜占庭節點的任意行為有可能誤導其他副本節點產生更大的危害,而不僅僅是宕機失去響應。)而早期的拜占庭容錯算法或者基於同步系統的假設,或者由於性能太低而不能在實際系統中運作。這篇論文中描述的算法是實用的,因為該算法可以工作在異步環境中,並且通過優化在早期算法的基礎上把響應性能提升了一個數量級以上。作者使用這個算法實現了拜占庭容錯的網絡文件系統(NFS),性能測試證明了該系統僅比無副本復制的標准NFS慢了3%。
1.概要介紹
第一段大片廢話就是說明拜占庭算法越來越重要了,然后說這篇論文提出解決拜占庭容錯的狀態機副本復制(state machine replication)算法。這個算法在保證活性和安全性(liveness & safety)的前提下提供了(n-1)/3的容錯性。從Lamport教授在1982年提出拜占庭問題開始,已經有一大堆算法去解決拜占庭容錯了。但是一句話概括:這些算法都是狗屎!PBFT算法跟這些妖艷賤貨完全不同,在只讀操作中只使用1次消息往返(message round trip),在只寫操作中只使用2次消息往返,並且在正常操作中使用了消息驗證編碼(Message Authentication Code,簡稱MAC),而造成妖艷賤貨性能低下的公鑰加密(public-key cryptography)只在發生失效的情況下使用。作者不僅提出算法,而且使用這個算法實現了一個牛逼的系統(拜占庭容錯的NFS),反正性能杠杠的。
作者先炫耀一下這邊論文的貢獻亮瞎你們的狗眼:
1)首次提出在異步網絡環境下使用狀態機副本復制協議
2)使用多種優化使性能顯著提升
3)實現了一種拜占庭容錯的分布式文件系統
4)為副本復制的性能損耗提供試驗數據支持
2.系統模型
這部分主要對節點行為和網絡環境進行劇情設定,然后賦予了消息的加密屬性,最后對大魔王(惡意節點)的能力進行設定。
系統假設為異步分布式的,通過網絡傳輸的消息可能丟失、延遲、重復或者亂序。作者假設節點的失效必須是獨立發生的,也就是說代碼、操作系統和管理員密碼這些東西在各個節點上是不一樣的。(那么如果節點失效不獨立發生,PBFT算法就失效了嗎?)
作者使用了加密技術來防止欺騙攻擊和重播攻擊,以及檢測被破壞的消息。消息包含了公鑰簽名(其實就是RSA算法)、消息驗證編碼(MAC)和無碰撞哈希函數生成的消息摘要(message digest)。使用m表示消息,mi表示由節點i簽名的消息,D(m)表示消息m的摘要。按照慣例,只對消息的摘要簽名,並且附在消息文本的后面。並且假設所有的節點都知道其他節點的公鑰以進行簽名驗證。
系統允許大魔王可以操縱多個失效節點、延遲通訊、甚至延遲正確節點來毀滅世界。但是作者限定大魔王不能無限期地延遲正確的節點,並且大魔王算力有限不能破解加密算法。例如,大魔王不能偽造正確節點的有效簽名,不能從摘要數據反向計算出消息內容,或者找到兩個有同樣摘要的消息。
3.服務屬性
這部分描述了副本復制服務的特性
論文算法實現的是一個具有確定性的副本復制服務,這個服務包括了一個狀態(state)和多個操作(operations)。這些操作不僅能夠進行簡單讀寫,而且能夠基於狀態和操作參數進行任意確定性的計算。客戶端向副本復制服務發起請求來執行操作,並且阻塞以等待回復。副本復制服務由n個節點組成。
針對安全性
算法在失效節點數量不超過(n-1)/3的情況下同時保證安全性和活性(safety & liveness)。安全性是指副本復制服務滿足線性一致性(linearizability),就像中心化系統一樣原子化執行操作。安全性要求失效副本的數量不超過上限,但是對客戶端失效的數量和是否與副本串謀不做限制。系統通過訪問控制來限制失效客戶端可能造成的破壞,審核客戶端並阻止客戶端發起無權執行的操作。同時,服務可以提供操作來改變一個客戶端的訪問權限。因為算法保證了權限撤銷操作可以被所有客戶端觀察到,這種方法可以提供強大的機制從失效的客戶端攻擊中恢復。
針對活性
算法不依賴同步提供安全性,因此必須依靠同步提供活性。否則,這個算法就可以被用來在異步系統中實現共識,而這是不可能的(由Fischer1985的論文證明)。本文的算法保證活性,即所有客戶端最終都會收到針對他們請求的回復,只要失效副本的數量不超過(n-1)/3,並且延遲delay(t)不會無限增長。這個delay(t)表示t時刻發出的消息到它被目標最終接收的時間間隔,假設發送者持續重傳直到消息被接收。這時一個相當弱的同步假設,因為在真實系統中網絡失效最終都會被修復。但是這就規避了Fischer1985提出的異步系統無法達成共識的問題。
下面這段話是關鍵
本文的算法彈性是達到最優的:當存在f個失效節點時必須保證存在至少3f+1個副本數量,這樣才能保證在異步系統中提供安全性和活性。這么多數量的副本是需要的,因為在同n-f個節點通訊后系統必須做出正確判斷,由於f個副本有可能失效而不發回響應。但是,有可能f個沒有失效的副本不發回響應(是因為網絡延遲嗎?),因此f個發回響應的副本有可能是失效的。盡管如此,系統仍舊需要足夠數量非失效節點的響應,並且這些非失效節點的響應數量必須超過失效節點的響應數量,即n-2f>f,因此得到n>3f。
算法不能解決信息保密的問題,失效的副本有可能將信息泄露給攻擊者。在一般情況下不可能提供信息保密,因為服務操作需要使用參數和服務狀態處理任意的計算,所有的副本都需要這些信息來有效執行操作。當然,還是有可能在存在惡意副本的情況下通過秘密分享模式(secret sharing scheme)來實現私密性,因為參數和部分狀態對服務操作來說是不可見的。
4.算法
PBFT是一種狀態機副本復制算法,即服務作為狀態機進行建模,狀態機在分布式系統的不同節點進行副本復制。每個狀態機的副本都保存了服務的狀態,同時也實現了服務的操作。將所有的副本組成的集合使用大寫字母R表示,使用0到|R|-1的整數表示每一個副本。為了描述方便,假設|R|=3f+1,這里f是有可能失效的副本的最大個數。盡管可以存在多於3f+1個副本,但是額外的副本除了降低性能之外不能提高可靠性。
PBFT的劇情緩緩展開,首先介紹舞台(view)、演員(replica)和角色(primary、backups)
所有的副本在一個被稱為視圖(View)的輪換過程(succession of configuration)中運作。在某個視圖中,一個副本作為主節點(primary),其他的副本作為備份(backups)。視圖是連續編號的整數。主節點由公式p = v mod |R|
計算得到,這里v是視圖編號,p是副本編號,|R|是副本集合的個數。當主節點失效的時候就需要啟動視圖更換(view change)過程。Viewstamped Replication算法和Paxos算法就是使用類似方法解決良性容錯的。
PBFT算法的狗血劇情如下:
1.客戶端向主節點發送請求調用服務操作
2.主節點通過廣播將請求發送給其他副本
3.所有副本都執行請求並將結果發回客戶端
4.客戶端需要等待f+1個不同副本節點發回相同的結果,作為整個操作的最終結果。
同所有的狀態機副本復制技術一樣,PBFT對每個副本節點提出了兩個限定條件:(1)所有節點必須是確定性的。也就是說,在給定狀態和參數相同的情況下,操作執行的結果必須相同;(2)所有節點必須從相同的狀態開始執行。在這兩個限定條件下,即使失效的副本節點存在,PBFT算法對所有非失效副本節點的請求執行總順序達成一致,從而保證安全性。
接下去描述簡化版本的PBFT算法,忽略磁盤空間不足和消息重傳等細節內容。並且,本文假設消息驗證過程是通過數字簽名方法實現的,而不是更加高效的基於消息驗證編碼(MAC)的方法。
4.1客戶端
客戶端c向主節點發送<REQUEST,o,t,c>
請求執行狀態機操作o,這里時間戳t用來保證客戶端請求只會執行一次。客戶端c發出請求的時間戳是全序排列的,后續發出的請求比早先發出的請求擁有更高的時間戳。例如,請求發起時的本地時鍾值可以作為時間戳。
每個由副本節點發給客戶端的消息都包含了當前的視圖編號,使得客戶端能夠跟蹤視圖編號,從而進一步推算出當前主節點的編號。客戶端通過點對點消息向它自己認為的主節點發送請求,然后主節點自動將該請求向所有備份節點進行廣播。
副本發給客戶端的響應為<REPLY,v,t,c,i,r>
,v是視圖編號,t是時間戳,i是副本的編號,r是請求執行的結果。
客戶端等待f+1個從不同副本得到的同樣響應,同樣響應需要保證簽名正確,並且具有同樣的時間戳t和執行結果r。這樣客戶端才能把r作為正確的執行結果,因為失效的副本節點不超過f個,所以f+1個副本的一致響應必定能夠保證結果是正確有效的。
如果客戶端沒有在有限時間內收到回復,請求將向所有副本節點進行廣播。如果請求已經在副本節點處理過了,副本就向客戶端重發一遍執行結果。如果請求沒有在副本節點處理過,該副本節點將把請求轉發給主節點。如果主節點沒有將該請求進行廣播,那么就有認為主節點失效,如果有足夠多的副本節點認為主節點失效,則會觸發一次視圖變更。
本文假設
客戶端會等待上一個請求完成才會發起下一個請求,但是只要能夠保證請求順序,可以允許請求是異步的。
4.2 PBFT算法主線流程(正常情況)
世界格局
每個副本節點的狀態都包含了服務的整體狀態,副本節點上的消息日志(message log)包含了該副本節點接受(accepted)的消息,並且使用一個整數表示副本節點的當前視圖編號。
事件的導火索
當主節點p收到客戶端的請求m,主節點將該請求向所有副本節點進行廣播,由此一場轟轟烈烈的三階段協議(three-phase protocol)拉開了序幕。在這里,至於什么消息過多需要緩存的情況我們就不管了,這不是重點。
三個階段的任務
我們重點討論預准備(pre-prepare)、准備(prepare)和確認(commit)這三個歷史性階段。預准備和准備兩個階段用來確保同一個視圖中請求發送的時序性(即使對請求進行排序的主節點失效了),准備和確認兩個階段用來確保在不同的視圖之間的確認請求是嚴格排序的。
預准備階段
在預准備階段,主節點分配一個序列號n給收到的請求,然后向所有備份節點群發預准備消息,預准備消息的格式為<<PRE-PREPARE,v,n,d>,m>
,這里v是視圖編號,m是客戶端發送的請求消息,d是請求消息m的摘要。
請求本身是不包含在預准備的消息里面的,這樣就能使預准備消息足夠小,因為預准備消息的目的是作為一種證明,確定該請求是在視圖v中被賦予了序號n,從而在視圖變更的過程中可以追索。另外一個層面,將“請求排序協議”和“請求傳輸協議”進行解耦,有利於對消息傳輸的效率進行深度優化。
備份節點對預准備消息的態度
只有滿足以下條件,各個備份節點才會接受一個預准備消息:
- 請求和預准備消息的簽名正確,並且d與m的摘要一致。
- 當前視圖編號是v。
- 該備份節點從未在視圖v中接受過序號為n但是摘要d不同的消息m。(許仙在這輩子從未見過名字叫白素貞的美貌女子)
- 預准備消息的序號n必須在水線(watermark)上下限h和H之間。
水線存在的意義在於防止一個失效節點使用一個很大的序號消耗序號空間。
進入准備階段
如果備份節點i接受了預准備消息<<PRE-PREPARE,v,n,d>,m>
,則進入准備階段。在准備階段的同時,該節點向所有副本節點發送准備消息<PREPARE,v,n,d,i>
,並且將預准備消息和准備消息寫入自己的消息日志。如果看預准備消息不順眼,就什么都不做。
接受准備消息需要滿足的條件
包括主節點在內的所有副本節點在收到准備消息之后,對消息的簽名是否正確,視圖編號是否一致,以及消息序號是否滿足水線限制這三個條件進行驗證,如果驗證通過則把這個准備消息寫入消息日志中。
准備階段完成的標志
我們定義准備階段完成的標志為副本節點i將(m,v,n,i)
記入其消息日志,其中m是請求內容,預准備消息m在視圖v中的編號n,以及2f個從不同副本節點收到的與預准備消息一致的准備消息。每個副本節點驗證預准備和准備消息的一致性主要檢查:視圖編號v、消息序號n和摘要d。
預准備階段和准備階段確保所有正常節點對同一個視圖中的請求序號達成一致。接下去是對這個結論的形式化證明:如果prepared(m,v,n,i)
為真,則prepared(m',v,n,j)
必不成立,這就意味着至少f+1個正常節點在視圖v的預准備或者准備階段發送了序號為n的消息m。
進入確認階段
當(m,v,n,i)條件為真的時候,副本i將<COMMIT,v,n,D(m),i>
向其他副本節點廣播,於是就進入了確認階段。每個副本接受確認消息的條件是:1)簽名正確;2)消息的視圖編號與節點的當前視圖編號一致;3)消息的序號n滿足水線條件,在h和H之間。一旦確認消息的接受條件滿足了,則該副本節點將確認消息寫入消息日志中。(補充:需要將針對某個請求的所有接受的消息寫入日志,這個日志可以是在內存中的)。
接受確認消息需要滿足的條件
我們定義確認完成committed(m,v,n)為真得條件為:任意f+1個正常副本節點集合中的所有副本i其prepared(m,v,n,i)為真;本地確認完成committed-local(m,v,n,i)為真的條件為:prepared(m,v,n,i)為真,並且i已經接受了2f+1個確認(包括自身在內)與預准備消息一致。確認與預准備消息一致的條件是具有相同的視圖編號、消息序號和消息摘要。
確認被接受的形式化描述
確認階段保證了以下這個不變式(invariant):對某個正常節點i來說,如果committed-local(m,v,n,i)為真則committed(m,v,n)也為真。這個不變式和視圖變更協議保證了所有正常節點對本地確認的請求的序號達成一致,即使這些請求在每個節點的確認處於不同的視圖。更進一步地講,這個不變式保證了任何正常節點的本地確認最終會確認f+1個更多的正常副本。
故事的終結
每個副本節點i在committed-local(m,v,n,i)為真之后執行m的請求,並且i的狀態反映了所有編號小於n的請求依次順序執行。這就確保了所有正常節點以同樣的順序執行所有請求,這樣就保證了算法的正確性(safety)。在完成請求的操作之后,每個副本節點都向客戶端發送回復。副本節點會把時間戳比已回復時間戳更小的請求丟棄,以保證請求只會被執行一次。
我們不依賴於消息的順序傳遞,因此某個副本節點可能亂序確認請求。因為每個副本節點在請求執行之前已經將預准備、准備和確認這三個消息記錄到了日志中,這樣亂序就不成問題了。(為什么?)
下圖展示了在沒有發生主節點失效的情況下算法的正常執行流程,其中副本0是主節點,副本3是失效節點,而C是客戶端。

4.3 垃圾回收
為了節省內存,系統需要一種將日志中的無異議消息記錄刪除的機制。為了保證系統的安全性,副本節點在刪除自己的消息日志前,需要確保至少f+1個正常副本節點執行了消息對應的請求,並且可以在視圖變更時向其他副本節點證明。另外,如果一些副本節點錯過部分消息,但是這些消息已經被所有正常副本節點刪除了,這就需要通過傳輸部分或者全部服務狀態實現該副本節點的同步。因此,副本節點同樣需要證明狀態的正確性。
在每一個操作執行后都生成這樣的證明是非常消耗資源的。因此,證明過程只有在請求序號可以被某個常數(比如100)整除的時候才會周期性地進行。我們將這些請求執行后得到的狀態稱作檢查點(checkpoint),並且將具有證明的檢查點稱作穩定檢查點(stable checkpoint)。
副本節點保存了服務狀態的多個邏輯拷貝,包括最新的穩定檢查點,零個或者多個非穩定的檢查點,以及一個當前狀態。寫時復制技術可以被用來減少存儲額外狀態拷貝的空間開銷。
檢查點的正確性證明的生成過程如下:當副本節點i生成一個檢查點后,向其他副本節點廣播檢查點消息<CHECKPOINT,n,d,i>
,這里n是最近一個影響狀態的請求序號,d是狀態的摘要。每個副本節點都默默地在各自的日志中收集並記錄其他節點發過來的檢查點消息,直到收到來自2f+1個不同副本節點的具有相同序號n和摘要d的檢查點消息。這2f+1
個消息就是這個檢查點的正確性證明。
具有證明的檢查點成為穩定檢查點,然后副本節點就可以將所有序號小於等於n的預准備、准備和確認消息從日志中刪除。同時也可以將之前的檢查點和檢查點消息一並刪除。
檢查點協議可以用來更新水線(watermark)的高低值(h和H),這兩個高低值限定了可以被接受的消息。水線的低值h與最近穩定檢查點的序列號相同,而水線的高值H=h+k,k需要足夠大才能使副本不至於為了等待穩定檢查點而停頓。加入檢查點每100個請求產生一次,k的取值可以是200。
4.4 視圖變更,改朝換代
使用計時器的超時機制觸發視圖變更事件
視圖變更協議在主節點失效的時候仍然保證系統的活性。視圖變更可以由超時觸發,以防止備份節點無期限地等待請求的執行。備份節點等待一個請求,就是該節點接收到一個有效請求,但是還沒有執行它。當備份節點接收到一個請求但是計時器還未運行,那么它就啟動計時器;當它不再等待請求的執行就把計時器停止,但是當它等待其他請求執行的時候再次情動計時器。