PBFT之三阶段提交
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为客户端.