PBFT之三階段提交
1 前言
Raft保證當復制狀態機數量為3f+1時, 最多可以允許f個狀態機虛假。
一個view中只有一個primary 其他為副本。
視圖更改說明primary崩潰或失敗。
2 算法流程
- 客戶端發送請求到primary調用服務操作
- primary廣播請求到所有節點
- 節點執行請求並返回響應到客戶端
- 客戶端等待從不同的節點發送的結果相同的f+1個響應。響應內容為操作的結果。
算法對節點的要求:
- 節點必須是確定性的(給予一系列參數執行操作必須產生相同的結果)。
- 節點必須以相同的狀態啟動
2.1 客戶端c
客戶端通過發送消息 <REQUEST,o,t,c>
到primary請求狀態機執行操作o。
t:時間戳用於確保該操作只執行一次,並且所有的請求都按照時間戳先后排序。
由節點發送到客戶端的消息包括(當前視圖號v,允許客戶端去跟蹤視圖發現當前的primary).
節點直接發送響應到客戶端,響應內容包括<REPLY,v,t,c,i,r>
.
v:當前視圖號。
t:響應請求的時間戳。
i:節點ID
r:執行操作得到的結果。
- 客戶端等待由不同的節點返回的具有相同的t和r的響應消息。
- 如果客戶端沒有接收到足夠的響應,將廣播請求到所有節點。如果請求已經被處理,節點只簡單地重新發送響應。
- 節點保留發送到每一個客戶端的最新的響應消息。
- 否則,如果節點不是primary,將會重定向請求到primary,如果primary沒有多播請求到集群,將會被懷疑是錯誤節點。如果有足夠多的節點懷疑則會發生視圖更新。
3 正常情況下三階段提交
每一個節點的狀態包括服務的狀態。消息日志包括節點被接受的信息,以及節點當前的視圖。
當primary接受到客戶端的請求m,將開始三個階段的協議進行自動多播請求到節點。
除非消息的數量超出協議中給定的最大消息數量否則primary立即開始該三階段協議。如果消息超過最大消息數,將會將請求放置緩沖區。
三階段分為pre-prepare,prepare,commit
。
pre-prepare
和prepare
階段用於對在同一視圖中發送的請求完全排序,即使提出請求排序的primary為虛假節點也是如此。prepare
和commit
階段用於確保在視圖之間對提交的請求進行完全排序
3.1 PRE-PREPARE階段
在pre-prepare
階段,primary定義了一個序列號n,到請求消息中。多播一個pre-prepare
消息並聯合消息m到所有節點。並將該消息添加到日志中。該消息內容為 <<PRE-PREPARE,v,n,d>_s,m>
(_s
代表簽名)這里的v表明被發送的消息處於的視圖。m是客戶端的請求消息。d為m的摘要。
為了保持消息較小。請求沒有包括在pre-prepare
消息中。這是很重要的因為pre-prepare
消息用於作為該請求定義的序列號n在視圖v中的證明。另外,它將協議與協議完全分離,以將請求傳輸到節點;允許我們為協議消息使用針對小消息優化的傳輸,對於大型請求針對大消息使用優化的傳輸。
節點接收到提供的pre-prepare消息后:
- 請求中的簽名和
pre-prepare
消息是有效的,並且d是m的摘要。 - 視圖v是有效的。
- 在視圖v中沒有接收到其他的具有序列號n的包含不同摘要的消息。
pre-prepare
消息中的序列號在低的閾值h與高閾值H之間。
最后一個條件用於阻止錯誤的primary為了耗盡序列號空間而選擇一個非常大的值。
如果節點i接受了 <<PRE-PREPARE,v,n,d>_s,m>
消息。節點將會進入prepare
階段,並多播 <PREPARE,v,n,d,i>_s
消息到所有其他的節點,並將該消息添加到它的日志中。否則將什么也不做。
3.2 PREPARE階段
節點(包括primary)接收了prepare
消息:
- 簽名是有效的
- 視圖號與節點當前視圖相同
- 序列號在h與H之間
並添加他們到自己的日志中。
只有當節點i已將以下消息添加到它的日志:
- 請求m
- 在視圖v中具有序列號n且是請求m的
pre-prepare
消息(來自不同節點2f個)
並且節點通過檢查prepare
消息與pre-prepare
消息具有相同的視圖,序列號和簽名,才認為prepared (m,v,n,i)
消息為有效的。
算法的pre-prepare
和prepare
階段保證誠實節點同意視圖中請求的總順序。更准確的,確保以下的變量:
- 對於任意的誠實節點j(包括i=j),如果
prepared (m,v,n,i)
消息是有效的,那么prepared (m’,v,n,j)
消息是無效的。並且任何D(m') 不等於D(m). - 因為
prepared (m,v,n,i)
消息和 R=3f+1表明至少有f+1個誠實節點在視圖v中發送了序列號為n的pre-prepare
消息或者是prepare
消息。 - 因此,對於
prepared (m’,v,n,j)
消息如果是有效的,那么需要至少一個誠實節點必須發送兩個沖突的prepare
消息(或者是視圖為v的primary
發送pre-prepare
消息),兩個prepare
消息具有相同的視圖和序列號但是具有不同的摘要信息。但是這是不可能的因為節點不是虛假節點。 - 最后,關於消息摘要強度的假設可確保m不等於 m' 並且 D(m) 等於 D(m') 是不可能的。
3.3 COMMIT階段
當prepared (m,v,n,i)
消息為有效的那么節點i多播 <COMMIT,v,n,D(m),i>_s
消息到其他節點.這個過程為commit
階段。節點接收commit
消息並添加該信息到日志中。
- 簽名是有效的
- 消息中的視圖號等於節點當前視圖號
- 序列號在h與H之間
如果並且只有當對於所有在f+1誠實節點中的節點i,prepared (m,v,n,i)
消息都是有效的,那么committed (m,v,n,i)
消息則是有效的。
如果並且只有當節點i從不同的節點接收到2f+1個commit
消息(可能包括自己),並且與請求m的pre-prepare
消息匹配(具有相同的視圖,序列號和摘要)。則committed-local (m,v,n,i)
消息是有效的。
commit
階段確保以下變量:
- 如果對於一些誠實節點i,
committed-local (m,v,n,i)
消息是有效的。那么committed(m,v,n)
消息是有效的。 - 誠實節點同意本地提交的請求的序列號,即使它們在每個節點上以不同的視圖提交,進一步,在誠實節點上本地提交的任何請求最終都將在1個或多個誠實節點上提交。
每一個節點i在當committed-local(m,v,n,i)
消息是有效的,並且i的狀態反應了在所有請求中該請求的序列號是最小的情況下將會執行該操作。確保了所有誠實節點可以以相同的順序執行請求,保證了安全性。在執行完請求操作后,節點將返回一個響應到客戶端。
當請求的時間戳小於最后一次回復的時間戳時節點拋棄該請求。保證只執行一次。
不依賴消息順序交付。因此可能節點亂序提交請求。這是無所謂的,因為節點保持了pre-prepare
,prepare
,和commit
消息日志一直到該請求被執行。
圖展示了該算法的以一種正常的例子(沒有primary虛假)的操作。節點0為primary,節點3為虛假節點。C為客戶端.