具体实现时可以联想到上面的一致性协议总的水线检查。上面的低水线h值等同于稳定检查点,稳定检查点之前的日志都可被清理掉。高水线H=h+k,也就是接收请求序号上限值,因为稳定检查点往往是间隔很多的序号才触发一次,所以k一般要设置的足够大。例如,每间隔100个请求就触发一次检查点协议,提升水线,k可以设置为200。
这里解释一下稳定检查点的概念,可以理解为当\(2f+1\)个节点都达到了某个请求序号,该请求序号就是稳定检查点。所有稳定检查点之前的消息都可以被丢弃,减少资源占用。 对比Raft,Raft是通过快照的方式压缩日志,都需要一个清理日志的机制,不然日志无限增长下去会造成系统不可用
视图更换协议在一致性协议里,已经知道主节点在整个系统中拥有序号分配,请求转发等核心能力,支配着这个系统的运行行为。然而一旦主节点自身发生错误,就可能导致从节点接收到具有相同序号的不同请求,或者同一个请求被分配多个序号等问题,这将直接导致请求不能被正确执行。视图更换协议的作用就是在主节点不能继续履行职责时,将其用一个从节点替换掉,并且保证已经被非拜占庭服务器执行的请求不会被篡改。即,核心有2点:1,主节点故障时,可能造成系统不可用,要更换主节点;2,当主节点是恶意节点时,要更换为诚实节点,不能让作恶节点作为主节点。
当检测到主节点故障或为恶意节点触发视图更换时,下一任主节点应该选谁呢?PBFT的办法是采用“轮流上岗”的方式,通过\((v+1) \ mod \ N\),其中\(v\)为当前视图号,\(N\)为节点总数,通过这一方式确定下一个视图的主节点。还有个更关键的问题,什么时候触发视图更换协议呢?我们继续往下讨论。
如果是主节点故障的情况,这种情况一般较好处理。具体实现时,一般从节点都会维护一个定时器,如果长时间没有收到来自主节点的消息,就会认为主节点发生故障。此时可触发视图更换协议,当然具体实现时,细节可能会不同,比如,也可以是这种情况,客户端发送请求给故障主节点必然导致长时间收不到响应,所以,客户端将请求发送给了系统中所有从节点,从节点将请求转发给主节点并启动定时器,如果主节点长时间没有将该请求分配序号发送PRE-PREPARE消息,认为主节点故障,触发视图更换协议。这2种情况比较好理解,但就这2种情况吗?其实还有以下几种情况也会触发视图更换协议:
从节点广播PREPARE消息后,在约定的时间内未收到来自其他节点的\(2f\)个一致合法消息。
从节点广播COMMIT消息后,在约定的时间内未收到来自其他节点的\(2f\)个一致合法消息。
从节点收到异常消息,比如视图、序号一致,但消息不一致。
这三点,都有可能是主节点作恶导致的,但也有可能是消息丢失等原因导致的。虽然不一定是因为主节点异常导致的,但从另一个角度看,解决了从节点不能无限等待其他节点投票消息的问题。
这里补充一点,触发视图更换协议后,将不再接收除检查点消息、VIEW-CHANGE消息、NEW-VIEW消息之外的消息。也就是视图更换期间,不再接收客户端请求,暂停服务。
解决了什么时候触发的问题后,下一个问题就是具体怎么实现呢?当因上面的情况触发视图更换协议时,从节点i就会广播一个VIEW-CHANGE消息<VIEW-CHANGE,v+1,n,C,P,i>,序号n是节点i的最新稳定检查点s,C是\(2f+1\)个有效检查点消息,是为了证明稳定检查点s的正确性,P是位于序号n之后的一系列消息的结合,这里要包含这些信息可以理解为是证据,也就是说,从节点不能随便就发送一个VIEW-CHANGE,什么证据都没有,别人怎么能认同你更换视图呢?。上面我们提到过下一任主节点是谁的问题?通过\((v+1) \ mod \ N\)确定的一下任主节点p(在图中就是节点1),在收到\(2f\)个有效的VIEW-CHANGE消息后,就广播<NEW-VIEW,v+1,V,O>消息,这里V和O具体的生成方法参考原论文,主要是VIEW-CHANGE和PRE-PREPARE等消息构成的集合,主要目的是为了让从节点去验证当前新的主节点的合法性以及解决下面这个问题,还有要处理未确认消息和投票消息。