最近研究了区块链相关的一些东西,其实就三大块: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,必须惟一
好吧,就这样吧,有问题再说。