最近研究了區塊鏈相關的一些東西,其實就三大塊:java
分佈式共識算法是分佈式系統的核心,常見的有Paxos、pbft、bft、raft、pow等。區塊鏈中常見的是POW、POS、DPOS、pbft等。 其中:node
區別:git
性能: 從上往下愈來愈高算法
總結:數據庫
那麼,下面咱們要說的就是聯盟鏈性質的共識協議:PBFT算法協議。 協議誕生已經好久了,網上的文章也很多,然而基本都是翻譯原論文,稍加一些我的的閱讀心得,細緻的分析仍是比較少,一些關鍵的銜接點沒有說清楚,粗看好像都懂,可是真正要實現起來,仍是有比較多坑。因而本人採用demo的方式,以多線程模擬多節點,實現完整的PBFT算法,其中有一些問題,記錄下來,供各位參考,討論。安全
主要有三個階段:預準備(pre-prepare)、準備(prepare)、和確認(commit)網絡
<<PRE-PREPARE,v,n,d>,m>
。<PREPARE,v,n,d,i>
<COMMIT,v,n,D(m),i>
整個協議理解起來還算比較簡單,可是這裏面有好些問題,須要一一的剖析。多線程
由於節點的操做都須要經過主節點,因此每一個節點啓動後的第一件事,找主節點。 主節點由公式p = v mod |R|計算獲得,這裏v是視圖編號,p是副本編號,|R|是副本集合的個數。 因此其實找主節點就是初始化視圖view。tcp
public static final int VIEW = -1;
if(this.viewOk)return; long count = vnumAggreCount.incrementAndGet(msg.getVnum()); if(count >= 2*maxf+1){ vnumAggreCount.clear(); this.view = msg.getVnum(); viewOk = true; System.out.println("視圖初始化完成["+index+"]:"+ view); }
誰先發現主節點失效了,固然,這裏是不能經過鏈接斷開來看,由於即便tcp鏈接正常,也不必定業務處理正常。答案是,超時控制。 當發起請求的節點在超時時間內沒有完成操做確認,則能夠懷疑主節點失效,因而:分佈式
if(!this.viewOk) return; // 已經開始選舉視圖,不用重複發起 this.viewOk = false; // 做爲副本節點,廣播視圖變動投票 PbftMsg cv = new PbftMsg(CV, this.index); cv.setVnum(this.view+1); PbftMain.publish(cv);
當每一個節點都收到2f+1個對同一個view的提議後,則切換成新的view。且檢查是否有請求待發送,一切恢復正常邏輯:
long count = vnumAggreCount.incrementAndGet(msg.getVnum()); if(count >= 2*maxf+1){ vnumAggreCount.clear(); this.view = msg.getVnum(); viewOk = true; System.out.println("視圖變動完成["+index+"]:"+ view); // 能夠繼續發請求 if(curMsg != null){ curMsg.setVnum(this.view); System.out.println("請求重傳["+index+"]:"+ curMsg); doSendCurMsg(); } }
提議時,若是有惡意節點重複屢次發起,須要檢測每一個節點只能投票一次。
String vkey = msg.getNode()+"@"+msg.getVnum(); if(votes_vnum.contains(vkey)){ return; } votes_vnum.add(vkey);
當commit經過後,即確認了操做,則能夠對該消息相關的狀態進行清理:
// 清理請求相關狀態 private void remove(String it) { votes_pre.remove(it); votes_pare.removeIf((vp)->{ return StringUtils.startsWith(vp, it); }); votes_comm.removeIf((vp)->{ return StringUtils.startsWith(vp, it); }); aggre_pare.remove(it); aggre_comm.remove(it); timeOuts.remove(it); }
PbftMsg消息的DataKey必須是惟一的,能夠經過uuid或者其餘方式定義
private int type; // 消息類型 private int node; // 節點 private int onode; // 發起請求的節點 private int vnum; // 視圖編號 private int no; // 序列號 private long time; // 時間戳 private String data; // 數據,表示數據的hash,必須惟一
好吧,就這樣吧,有問題再說。