Commit 36f78027 authored by Hugo's avatar Hugo

update

parent 1957303f
This source diff could not be displayed because it is too large. You can view the blob instead.
# pbft共识
# pbft共识
## 概念
pbft全称(Practical Byzantine Fault Tolerance)意为实用拜占庭容错算法.该算法是Miguel Castro (卡斯特罗)和Barbara Liskov(利斯科夫)在1999年提出来的,解决了原始拜占庭容错算法效率不高的问题,将算法复杂度由指数级降低到多项式级,使得拜占庭容错算法在实际系统应用中变得可行。该论文发表在1999年的操作系统设计与实现国际会议上(OSDI99)。这个Loskov就是提出著名的里氏替换原则(LSP)的人,2008年图灵奖得主。
## 算法概要
![pbft一般流程](./resources/pbft-normal-case-operation.png)
* 客户端发送请求到primary节点,primary由公式p = V mod R决定,p是节点的编号,V是视图号,R是整个系统中节点(或称为副本)的个数。请求的消息格式为<REQUEST,o,t,c>,其中o是请求的操作,t是客户端的本地时间,c是客户端;
* Primary广播请求消息到每一个backup节点;
* 所有节点(primary和backups)执行请求,把结果返回到客户端,返回的消息格式为<REPLY,v,t,c,i,r>,v是节点维持的当前的视图号,t是与请求消息中一样的时间,i是节点的编号,r是执行请求的结果;节点会把时间戳比已回复时间戳更小的请求丢弃,以保证请求只会被执行一次.
* 如果客户端收到f+1个来自不同节点且t相同、r也相同的应答,则接受此应答。如果客户端一直没有收到应答,则客户端向每一个节点都发送此请求消息,如果此请求已经被执行,则直接把结果返回给客户端(每个节点保存上一个发送给客户端的应答);如果此请求还没执行,那么非primary节点把此请求转发给primary。如果primary一直不广播请求,backups收不到请求,则怀疑primary节点出错,导致view change。
此过程允许客户端发送异步请求,并保留对它们的排序约束.
## 算法详细步骤
本算法分为三个阶段:pre-prepare、prepare 和 commit和三个谓词(predicate)prepared、committed和committed-local.Pre-prepare和Prepare保证同一个view中的请求有序,Prepare和Committe保证不同view中的请求被有序地执行。
1. 当primary节点收到请求时,则开始pre-prepare阶段:primary对收到的请求标记序号n(n是按序增长的),把pre-prepare消息进行广播,并把此消息记入自己的log中。消息格式为<PRE-PREPARE,v,n,m>,v是primary节点当前的视图号,n是这个请求的序号,m是请求。
Backup节点收到<PRE-PREPARE,v,n,m>消息时进行检查,如果满足:
(1) v与自己的视图号相同,
(2) n在[h,H]之间,
则接受此<PRE-PREPARE,v,n,m>消息。
2. prepare阶段
如果一个backup节点i接受<PRE-PREPARE,v,n,m>消息,它就广播一个prepare消息(消息格式为<PREPARE,v,n,d,i>,其中d是m的digest)到其他所有节点,并把<PRE-PREPARE,v,n,m>消息和<PREPARE,v,n,d,i>消息都记入自己的log中,这进入prepare阶段。
- 我们定义谓词prepared(m,v,n,i)为true,当且仅当一个节点i记入其log中:请求m,针对在视图v序号为n的消息m的PRE-PREPARE,2f个来自不同节点且与PRE-PREPARE匹配的<PREPARE,v,n,d,i>消息(匹配的条件是要有相同的v和相同的n和d)。
3. commit阶段
如果一个节点i的谓词prepared为true,则广播commit消息到每一节点,这就进入了确认阶段。所有节点在一定前提(签名正确,视图v与当前的v一致,序号n在h与H之间)下接受commit消息(消息格式为<COMMIT,v,n,d,i>)并记入log中。
- 我们定义谓词committed为true当且仅当f+1个非故障节点(non-faulty)中的任意副本i的谓词prepared为真.
- 定义谓词committed-local当且仅当该节点谓词prepared为真,并且接受了2f+1个(可能包括他自己)来自不同节点的与PRE-PREPARE匹配的<COMMIT,v,n,d,i>消息(匹配条件是要有相同的n、相同的v、相同的d)。
## 垃圾回收
为了保持安全条件,消息必须保存在节点的日志中,直到它知道它们所关注的请求已由至少f + 1个非故障节点执行,并且它可以在视图更改(view change)时向其他人证明这一点。此外,如果某节点错过了所有非故障副本丢弃的消息,则需要通过传输全部或部分服务状态来更新这个节点。因此,节点也需要一些证明状态正确的证据。但节点的日志不能无限制地增长,当到达某一时刻,节点要删除一些老旧的log。又不能执行过一个请求后就把相关的log立刻删除,这样是不安全的.
检查点(checkpoint):当一个节点收到一个request的序号为n,而n能被一个整数K(例如100)整除时,我们把此时的这个状态叫做checkpoint,处理改消息的时机是完成reply之后。
稳定检查点(stable checkpoint):如果一个节点收到2f+1个来自不同节点(包括它自己的)的<CHECKPOINT,v,n,d,i>消息且有相同的n、相同的d,那么,这2f+1个消息就是checkpoint的证据,我们把一个拥有证据的checkpoint叫做stable checkpoint。
每个节点都保存有3种state:
- 上一个stable checkpoint;
- 0个或多个不是stable的checkpoint;
- 当前的state(具体指什么?代码中使用的lastReply+view)。
产生过程如下:
1. 当一个节点i产生checkpoint时,它广播一个checkpoint消息到其他所有节点,消息格式为<CHECKPOINT,v,n,d,i>,n是上一个产生稳定的检查点的请求的序号,v是n对应的视图号,d是n对应的请求的摘要。
2. 每个节点在其log中收集checkpoint消息,直到它具有2f + 1个序列号为n,由不同的副本签名(可能包括其自己的此类消息)的相同的摘要d.这2f+1个消息就是检查点正确性的证据.
一个节点达到稳定检查点之后,可以丢弃序号小于或等于n所有的pre-prepare,prepare和commit log信息;也丢弃所有早期的checkpoints和checkpoint消息.
检查点协议被用于提出高低水位标记(限制接受哪些消息).低水位标记h等于上一个stable checkpoint对应的n的序号,高水位标记H=h+k,k足够大,使节点不至于为了等待稳定检查点而停顿.例:如果每100个请求产出一次检查点,k可以设为200.
## 视图切换(view change)
视图变更协议在主节点失效的时候仍然保证系统的活性。视图变更可以由超时触发,以防止备份节点无期限地等待请求的执行。如果一个备份节点收到有效请求,但是还没有执行它,我们就说它在等待一个请求。当备份节点接收到一个请求但是定时器还未运行,那么它就启动定时器;当它不再等待执行请求就把定时器停止,但是如果它正等待执行其他请求时就要重启定时器。如果定时器超时,则触发view change。
![视图切换](./resources/viewchange.png)
过程如下:
1. 当一个backup节点i的timer超时,i停止接收消息(但仍然接收checkpoint、view change和new view消息),并广播一个view change消息,消息格式为<VIEW-CHANGE,v+1,n,s,C,P,i>,n是节点i中上一个stable checkpoint(s) 对应的序号,C是s的证据(2f+1个有效的checkpoint消息),P是i中每一个序号大于n的请求所对应的prepared为true的log(证据)的集合。
2. 如果v+1对应的新primary节点收到2f+1个来自不同节点(包含自己)的<VIEW-CHANGE,v+1,n,s,C,P,i>消息,它就广播new view消息到所有其他节点,消息格式为<NEW-VIEW,v+1,V,O>,V是包含接收到的其他节点的view change消息和自己发送(或将要发送)的view change消息的集合.O是pre-prepare消息(不带请求m)的集合.
- O通过下面的方法计算得到:
1. primary确定pre-prepare消息的序号范围从V中上一个stable checkpoint对应的序号min-s,到V中的prepared为true的证据中的最大的序号max-s.
2. primary对每一个序号为n(第一点中确定的范围)的请求创建一个新的且视图号为v+1的pre-prepare消息.此时有两种情况:
- 在V中,存在序号为n的prepared的证据.
- 不存在这样的prepared证据.
第一种情况下O是primary创建的针对每个n的新<PRE-PREPARE,v+1,n,d>消息.
第二中情况下O是primary创建的一个<PRE-PREPARE,v+1,n,null>消息,null是一种指定的“null”请求(此请求不做任何操作)的digest,并把此消息记入log.
如果min-s大于primary上一个stable checkpoint对应的序号,那么primary将stable checkpoint的消息保存到log,从而更新自己的stable checkpoint。此时,primary节点进入view为v+1阶段,并可以接收视图号为v+1的消息.
3. backup节点通过如下验证来决定是否接受new view消息:
- 正确的签名信息
- 其包含的view change消息(也就是V)对v+1是否有效.
- 通过类似于主节点创建O的方法计算验证O的正确性.
4. 接下来的操作就是重做从min-s到max-s序号之间的prepare流程,但是为避免重新执行客户端的请求(通过使用他们存储的发送给每个客户端的最后回复的信息).
5. 如果一个节点丢失了一些请求消息m或者一个stable checkpoint(由于这些没有在new-view消息中发送)可以从其他节点获取缺少的信息.例如,节点i可以从任何一个在V中证明了checkpoint 消息正确性的节点处获取缺少的checkpoint state s.由于f+1个节点是正确的,节点i将总能获得s或稍后认证的stable checkpoint。
### 代码中的不同
1. pset是达到prepared的序号的结果描述而不是序号对应的prepare集合+m+pre-prepare,而且又定义了qset用于保存收到或发送过pre-prepare的序号的结果.
2. viewchange消息包含view,min-s,C,pset,qset,id
3. new-view消息包含view,V,(n,d),id在viewchange.go中260行assignSequenceNumbers方法中实现,其中做了好多重循环,应该可以优化,感觉过于复杂其目的就是为了找到O中需要的序号n和d的对应关系,用于后面每个节点重新构建pre-prepare消息,非主节点发送prepare消息.
## TODO
1. pre-prepare验证通过后启动定时器.
2. log持久化可以帮助节点启动后的恢复,垃圾回收时删除对应的log文件.
3. 每个节点reply之后启动定时器,如超时则启动viewchange(空节点?);没有收到主节点心跳则触发viewchange;发起viewchange请求报文后,没有在规定时间完成,重新触发viewchange事件;mempool有交易启动定时器准备viewchange?
4. 节点启动时的状态统一:
1. 节点启动后根据自己的log文件中记录的最近的消息的view,发送消息给所有节点,进行view协商.
2. 落后副本节点的状态(stable checkpoint)恢复:2f+1的节点会发送自己的稳定检查点消息给f节点,使其从该稳定检查点继续进行.
5. pbft.go中1195-1207执行完一个请求后,发现有未处理的请求时,先执行recvClientRequest还是先checkpoint?
6. pbft.go中734行,如果没有发送过checkpoint就返回false?如果该节点处理比较慢还没发送checkpoint,那么就不接受别的节点的消息?
7. pbft.go中1248行在达到stable checkpoint的时候,持久化checkpoint论文中包含收到的所有checkpoint消息,而且需要丢弃旧的stable checkpoint.
8. 论文中pre-prepare消息是与消息内容是分离的,可以先发REQ再发pre-prepare消息.
9. 其他TODO
## 疑问
1.一个节点的同一个prepare消息能否发送多次?
2.每个节点的checkpoint是否一样?
3.change view时旧view的pre-prepare,prepare,commit消息是否可以删除?
## 讨论
1. 文档中描述prepared的条件有三个:
* 消息写入日志
* 包含m,v,n的pre-prepare消息
* 匹配pre-prepare消息的来自不同backups的2f个prepare消息
当4个节点(包含主节点在内)中有一个backup节点failed的情况下,如何另外两个backup节点如何收到2f个别的backups的prepare消息?
理解:
backups节点发送prepare消息给所有节点(包括自己),在n=4,f=1时,
* 如果是主节点failed,则三个backups节点不会发送prepare消息,启动视图切换;
* 如果是某个backup节点failed,则每个backup节点会收到1个prepare消息和自己的prepare,以及主节点的preprepare(意味着prepare),这时也是符合prepared条件的;主节点收到2个backups的prepare消息.
* 如果没有failed节点,则主节点会收到3个prepare消息,每个backup节点会收到2个prepare消息和自己的prepare.
File added
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment