我对PBFT的理解

221 阅读12分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

定义

实用拜占庭容错算法 Practicl Byzantine Fault Tolerance 由卡斯特罗(Miguel Casstro)和利斯科夫(Barbara Liskov) 在1999年提出来的,

  • 解决原始拜占庭容错算法效率不高,信道必须可靠(未考虑消息延迟,消息丢失)问题。
  • 满足将军总数大于n,背叛将军个数为f,n>3f+1情况下,忠诚的将军能达成共识。
  • 将算法复杂度由指数级nmn^m( n为节点总数,m为恶意节点数)降到多项式级n2n^2( n为节点总数)

算法协议

  • 三阶段提交协议:解决达成共识问题
  • 视图更换协议:用于解决主节点失效,或主节点恶意节点情况。一个视图代表着某个节点作为主节点,更换一个视图代表更换了一个主节点。
  • 检查点协议: 标记已经完成共识的检查点,用于垃圾回收

其它算法

  • 摘要算法 防止消息篡改
  • 非对称密钥签名、验签算法。仅仅在 视图切换view-change和new-view消息使用
  • 消息验证码算法MAC 提升签名性能,对比非对称密钥签名MAC算法性能更高。防止消息篡改,并能验证数据发送方。

名词解释

  • 主节点primary:负责对请求进行排序,转发客户端请求
  • 副节点backupa:负责验证请求是否有效,并和主节点达成共识
  • 客户端client:发送请求,要求节点执行某个操作
  • 序列号sequence number :对请求进行编号,用于请求排序
  • 视图view :一个主节点和多个备份节点形成一个视图,一个节点作为一个新的主节点代表一个一个视图的开始,更换节点会更换视图。
  • 检查点checkpoint: 某个序列号对应的请求收到了超过2/3的节点确认,称为一个检查点

基本流程

其中n为总节点个数,f为拜占庭节点个数

  1. 客户端发送请求给主节点,要求执行操作
  2. 主节点接受客户端请求,进入预准备PRE-PREPARE阶段,在本地日志记录PRE-PREPARE消息,并广播PRE-PAREPARE到其它所有副节点。
  3. 副节点收到主节点PRE-PAREPARE节点,对消息进行验证。验证通过后,进入准备PREPARE阶段,广播PREPARE消息给其它所有节点
  4. 每个节点收到其它节点PREPARE消息后,进行消息验证,当接收到2f+1个验证通过的PREPARE消息(包含自己),在本地日志中记录,PRE-PREPARE、PREPARE消息,进入提交COMMIT阶段,并广播COMMIT消息给所有节点
  5. 每个节点收到其它节点COMMIT消息后,进行验证,当接收到2f+1个验证通过的COMMIT消息(包含自己),在本地日志中记录,COMMIT消息。广播REPLY消息发送给客户端。
  6. 客户端收到节点REPLAY消息,进行验证,客户端阻塞等待,当收到f+1个REPLAY消息并验证通过,则返回结果。

三阶段提交协议

初始化阶段,主节点pp = v mod n计算得出。
结合下图说明三阶段提交协议,pbft达成共识的过程。
下图中C代表客户端,0,1,23分别代表3个节点。其中0为主节点,1,2为正常工作节点,3为异常宕机节点。
在pbft算法中,客户端读写请求都是首先发送给主节点,主节点发送请求给其它节点进行共识。 图一

结合下3阶段协议图,按阶段进行说明

REQUEST

客户端c在时间t,执行操作o,客户端将消息组装为REQUEST消息并对消息进行签名σc,格式<REQUEST,o,t,c>σc,将消息发送给主节点。

PRE-PREPARE

主节点收到客户端请求,进入预准备PRE-PREPARE阶段。
基于当前视图v对请求分配编号n,并对客户端请求m(可以理解为<REQUEST,o,t,c>σc)进行摘要,摘要结果为d,封装PRE-PREPARE并对消息进行签名,签名记为σp。 格式为<PRE-PREPARE,v,n,d>σp
记录PRE-PREPARE消息到本地日志中,记录客户端请求m到本地日志中,广播PRE-PREPARE消息以及客户端请求m<<<PRE-PREPARE,v,n,d>σp>,m>给其它副节点。 PRE-PREPARE阶段,主要对请求分配编号对请求进行排序。

PREPARE

副节点收到主节点PRE-PAREPARE消息,对消息进行验证。满足一下条件表示验证通过,验证不通过则会拒绝该消息

  • 副节点当前视图也是v,表明PRE-PREPARE消息发起者确实是主节点
  • 客户端请求m的摘要是PRE-PREPARE中的d,PRE-PREPARE签名σp,验签正确。客户端请求m中客户端验签正确保证消息未被篡改(如客户端消息被截获,主节点作恶篡改消息)且由客服端发送。
  • 副节点当前视图v中,序列号n未被使用过。即该节点未接受过编号同样为n,但请求不为m的PRE-PAREPARE消息
  • 序列号n处于高低水位值之间,即n∈[h,H],h为低水位,H为高水位,一般H=h+k,k为常量。这个限制是为防止某个主节点作恶,使用过大序列号恶意消耗序列号空间。 每个副节点验证PRE-PREPARE通过后,进入PREPARE阶段,组装PREPARE消息,格式为<PREPARE,v,n,d,i>σiv,n,d为当前视图编号,请求序列号,客户端消息摘要,i为当前节点编号σi为当前节点i对PREPARE消息的签名。
    副节点将PREPARE消息记录到本地日志中,并广播PREPARE给其它所有节点。
    副节点在PREPARE阶段,表明当前节点接受了主节点的提议,同意在视图v中把序列号n分配给客户端请求m

COMMIT

每个节点(包括主节点)收到其它节点的<PREPARE,v,n,d,i>σiPREPARE消息,会进行验证。

  • 客户端请求m的摘要是PREPARE中的d,PREPARE消息的签名σi验证正确
  • 当前节点视图编号为v
  • 在视图v中,请求序列号n处于高低水位值之间,即n∈[h,H] 每个节点当接收到2f+1(包含自己)个验证通过的PREPARE消息后,节点进入COMMIT阶段,组装COMMIT消息,格式为<COMMIT,v,n,D(m),i>σi,v,n,D(m)为当前视图编号,请求序列号,客户端消息摘要,i为当前节点编号σi为当前节点i对COMMIT消息的签名。
    节点将COMMIT消息记录到本地日志中,并广播给其它节点。

REPLY

注意REPLY不属于3阶段提交中的3阶段。3阶段是指PRE-PREPARE,PREPARE,COMMIT 3个阶段。
每个节点收到其它节点的<COMMIT,v,n,D(m),i>σi后,会进行验证。

  • COMMIT消息的摘要D(m)、签名σi验证正确
  • 当前节点视图编号为v
  • 在视图v中,请求序列号n处于高低水位值之间,即n∈[h,H] 每个节点当接收到2f+1(包含自己)个验证通过的COMMIT消息,会执行客户端消息m中的操作o,进入REPLAY阶段,组装REPLAY消息,格式为<REPLAY,v,t,c,i,r>σi,v为当前视图编号,t为客户端发送请求时间戳,c为client指发送给哪个客户端,i为当前节点编号,r为节点i执行操作的回复,σi为节点对消息的签名。

客户端接受处理

客户端收到节点的<REPLAY,v,t,c,i,r>σiREPLAY消息,

客户端接受到f+1个REPLAY消息后,验证REPLAY消息签名σi验证正确,t时间戳等于发送REQEUST消息时间戳,所有r一致,会返回结果。

异常情况

  • 主节点作恶(如:不转发客户端消息、篡改客户端消息、对不同节点发送不同客户端消息),会引发视图变更,替换主节点
  • 主节点故障,引发视图变更,替换主节点
  • 客户端超时未收到响应,客户端会广播请求给所有的节点。节点收到重复请求,如果请求已经处理过,会将请求再次发送给客户端。如果请求未处理过,副节点会将请求转发给主节点。

2f+1和f+1

在COMMIT阶段和REPLAY阶段,都要受到2f+1个节点响应才进入下一阶段,而客户端仅仅等待f+1个响应就返回。
在COMMIT阶段和REPLAY阶段,拜占庭容错能支持f作恶节点和f个宕机节点同时存在,当收到2f+1个响应时,最多有f个作恶节点,其它f+1节点为正常节点,根据多数派原则少数服从多数,能判断出正确的响应,因此需要是2f+1。
在客户端响应阶段,客户端收到f+1个响应,最坏有f个作恶节点,另外1个肯定是正常节点,1个正常节点发送了响应消息,说明该节点已经完成了COMMIT阶段的共识,已有2f+1个节点完成COMMIT,执行了客户端的请求操作。因此客户端只需要f+1个响应即可。

检查点协议

PBFT通过三阶段协议对请求达成共识,每一阶段都会进行本地日志记录,如果不进行垃圾回收,删除旧日志记录,会增加系统存储资源负担。PBFT算法设计了检查点协议丢弃本地消息日志中的就消息。

根据前面的三阶段协议,客户端收到某个请求执行结果的时候,表明该请求至少已经被f+1个节点提交过了,这个时候可以删除旧消息了,但是每次收到执行结果时去删除旧消息,会比较浪费资源。因此PBFT采用周期性清除,周期记为T,比如当请求序号整除100时进行清除(批量清除的意思)T为每隔100个消息进行清除。

检查点协议工作流程

当周期T到达时,每个节点会组装CHECKPOINT消息,消息格式为<CHECKPOINT,n,d,i>σi,n为最近一次请求的序列号,d为最近一次请求的客户端请求摘要,σi为CHECKPOINT消息的签名。节点广播消息给其它节点。
每个节点收到2f+1个CHECKPOINT消息验证签名通过后,表明n是一个稳定的检查点stablecheckpoint。
每个节点把共识后的CHECKPOINT消息记录到本地日志。
当一个检查点证明有效、稳定后,节点会把本地消息日志中客户端请求序列小于或等于n的消息(PRE-PREPARE,PREPARE,COMMIT)都删除掉,同时也会删掉旧的检查点和CHECKPOINT消息。

高低水位线

最新的稳定检查点statblecheckpoint即为低水位线记为h,高水位线记为H,H=h+kk为一个可设置数的常量。若客户端请求序号为n,n>H时请求会被阻塞,直至高低水位线更新。
当稳定检查点更新时,高低水位线也会进行更新。
其中k要比checkpoint的周期T要大,比如T为100,k可以为200

视图更换协议

PBFT使用视图变更协议允许主节点出故障情况下仍然能够正常运行,保证系统活性。视图变更协议是通过超时机制触发的。

视图变更超时机制

论文中提到:当节点收到一个有效的请求,而且这个请求没有被执行过的时候,当前视图如果没有启动计时器则启动一个新的计时器,等待超时,超时时间内如果在接收到这个请求则停止计时器。
解释
客户端发送了请求给主节点,客户端等待响应超时。
客户端重新发送了请求给所有副本,副本验证客户端请求通过,并判断这个请求自己并未执行过,说明主节点未转发客户端请求给副节点。
副节点针对当前视图启动一个新定时器,如果当前视图还没有定时器,并将请求转发给主节点。
在超时时间内,若主节点重新将请求触发3阶段协议转发给副节点,定时器停止。
若在超时时间内,副节点未收到重新发送的请求,启动视图更换协议,请求视图变更,更换主节点。

注意:在视图变更期间,除了CHECKPOINT,VIEW-CHANGE和NEWVIEW消息外,副节点不会接受其它消息。

视图变更流程

每个副节点在视图v计时器超时,副节点会提议系统所有节点切换到下一个视图v+1,此时除了CHECKPOINT,VIEW-CHANGE和NEWVIEW消息外,副节点不会接受其它消息。副节点组装VIEW-CHANGE消息,消息格式为<VIEW-CHANGE,v+1,n,C,P,i>σi,其中n为最后一个稳定检查点statblecheckpointsC为证明该稳定检查点s的CHECKPOINT消息集合。PPm的集合,m是副节点i中序列号大于n的PRE-PAREPARE和PREPARE的客户端请求m。Pm包含以下信息

  • 不包含原始请求m的PRE-PREPARE消息
  • 由2f个其它副节点签名的和PRE-PREPARE消息匹配的PREPARE消息。(匹配是指PREPARE消息签名有效,在视图中分配的请求序列号一致,客户端请求摘要相同) 当新的主节点(由 p = v+1 mod n计算得到)在v+1视图,收到2f个节点发送的有效的切换视图为v+1的VIEW-CHANGE消息,会向其他节点发送NEW-VIEW消息,消息格式<NEW-VIEW,v+1,V,O>σp
    V是新的主节点p收到的有效的视图标号为v+1的VIEW-CHANGE消息集合。
    O是上一个视图中未处理完的有效PRE-PREPARE消息集合。
    注意:假设O中包含的PRE-PREPARE消息编号范围为min~max,min为V中的statabelcheckpoint,若max>min则组装新消息<PRE-PREPARE,v,n,d>σp。若mx=min,组装消息<PRE-PREPARE,v,n,d(null)>σp
    其它节点收到NEW-VIEW消息后,验证消息有效性,切换视图到v+1,对O中的PRE-PREPARE消息重新执行一遍三阶段协议。

参考

pbft算法原文