—— 前言 ——
我们已经了解到分布式系统一般通过状态复制机[1]原理来实现一致性。其核心思想是系统中所有副本运行着相同的状态机,只要所有副本都以相同的初识状态开始,并基于相同的初识状态执行一组相同顺序的操作,那么所有的状态最终会收敛一致,即整个系统对外表现出一致性。而确定这一组相同顺序的操作需要系统达成共识,进一步说即所有诚实节点对执行顺序达成共识,这便是著名的拜占庭将军[2]问题。
拜占庭类共识算法的理论安全保证,即n>3f,n为总的节点数量,f为恶意节点数量。一个拜占庭共识算法需要保证两个性质:
**安全性:**所有诚实节点都认为某一时刻系统状态为s
**活性:**所有诚实节点最终能确定s为系统状态
其中s是一个抽象的概念,可以理解为系统内存在一个变量S,这个变量S的取值为系统状态,系统内节点接收一系列关于S的操作指令,某时刻(假设每个节点时钟没有误差)就S的取值进行共识,所有诚实节点确定变量S=s则满足安全性,所有诚实节点对变量S的取值必须做出决定且终止则满足活性。
以往,在分布式系统的研究中,拜占庭类共识往往都伴随着较高的通信复杂度,对网络造成的消耗极大,系统规模不易扩大。如经典的PBFT算法,其达成共识需要经过三个阶段,PRE-PREPARE阶段主节点将请求消息发送给其他节点,其他节点对该消息验证过后,各自发送prepare消息给其他节点,这个阶段产生了n2条消息。为了保证跨视图上的一致性,节点在收到至少quorum的prepare消息后再发送commit消息给其他节点。当节点最终收到至少quorum的commit消息时,再最终对该请求进行提交。而网络异常节点超时触发视图切换时,则需要o(n3)的通信复杂度
理解PBFT中每个阶段的工作是HotStuff的基础,PBFT中每个阶段都目标都是为保证安全性和活性。
假设系统某时刻收到指令S’=S+1,主节点将这条指令S’=S+1发送给非主节点(这是pre-prepare消息),因为是拜占庭问题,诚实节点不确定自己收到的是否和其他诚实节点一致(主节点发送不一致消息),节点之间需要进行一次相互通信,确定自己和其他诚实节点收到的消息一致(确定主节点没有发送不一致消息),每个节点发送prepare消息给其他所有节点,之后若收到quorum个数量的prepare消息,且通过验证(验证prepare中指向的指令与自己一致),达成第一轮共识(这是PREPARE阶段)。此时,似乎可以确定了诚实节点收到的消息一致。但是这里隐含条件是,达到共识的节点只是在自己的视角看到:我收到并验证了quorum个一致的消息。但其他节点不一定和自己一样收到quorum个prepare消息达成共识,如果此时进行提交,出现了网络故障,提交了的节点知道已经达成共识,只要等网络恢复,这条指令一定会被整个系统提交。但其他节点可能由于网络故障还未达成共识,他们无法确定一直等下去能否提交。为了让系统保持活性,进行视图切换,此时新的主节点需要确定是在S’还是S的基础上执行新的指令。如果没有发现部分节点已经提交了该指令,且对另一条指令S’’=S+2进行了共识并提交,系统对变量S的取值产生了不一致。因此,在此时提交有安全性的问题。
如果再进行一个阶段的共识,在达成PREPARE共识之后,各自再发送一个commit消息,各自节点等待接受并验证quorum个commit消息之后再提交。会遇到同样的问题,达成COMMIT共识的节点提交了,他们知道如果网络正常,系统迟早都会提交,但是其他未达成COMMIT共识的节点不确定最后能否提交。网络发生故障后新视图中的主节点如果没有发现已经提交了的节点,依然会造成不一致。这里的矛盾是我们为了活性需要切换视图继续共识;为了安全性还要确保新视图开始共识前S的取值一致,已经提交了的指令在