瓴彩釒

PBFT核心由3个协议组成:一致性协议、检查点协议、视图更换协议。

 

系统正常运行在一致性协议和检查点协议下,只有当主节点出错或者运行缓慢的情况下才会启动视图更换协议,以维持系统继续响应客户端的请求。下面详解这3个子协议。在讲一致性协议之前,我们屏蔽算法细节先看一下正常情况下大致是怎么工作的,大致流程如下:

  1. 客户端发送请求给主节点(如果请求发送给了从节点,从节点会将该请求转发给主节点或者将主节点的信息告知客户端,让客户端发送给主节点)。
  2. 主节点将请求广播给从节点。
  3. 主从节点经过2轮投票后执行客户端的请求并响应客户端。(协议细节见下面的一致性协议)
  4. 客户端收集到来着f+1 f+1 个不同节点的相同的响应后,确认请求执行成功。(因为最多有f f 个恶意节点,f+1 f+1 个相同即能保证正确性)。

一致性协议

一致性协议的目标是使来自客户端的请求在每个服务器上都按照一个确定的顺序执行。 在协议中,一般有一个服务器被称作主节点,负责将客户端的请求排序;其余的服务器称作从节点,按照主节点提供的顺序执行请求。所有的服务器都在相同的配置信息下工作,这个配置信息称作视图view,每更换一次主节点,视图view就会随之变化。协议主要分pre-preparepreparecommit三阶段,如下图所示:
瓴彩釒_第1张图片

REQUEST:

首先是客户端发起请求, 请求中时间戳t主要用来保证exactly-once语义,也就是说对同一客户端请求不能有执行2次的情况,具体实现时也不一定非是时间戳,也可以是逻辑时钟或者其他,只要能唯一标识这个请求就可以了。

PRE-PREPARE:

【1】 收到客户端的请求消息后,先判断当前正在处理的消息数量是否超出限制,如果超出限制,则先缓存起来,后面再打包一起处理。否则的话(当然,没超过也可以缓存处理),对请求分配序列号n,并附加视图号v等信息生成PRE-PREPARE消息<,m>,广播给其他节点。简而言之就是对请求分配序号并告知所有节点。

【2】 收到PRE-PREPARE的消息后进行如下处理:

  • 消息合法性检查,消息签名是否正确,消息摘要是否正确。
  • 视图检查,检查是否是同一个视图号v
  • 水线检查,判断n是否在hH之间。(h一般是系统稳定检查点,H是上限,会随着h的不断提高而提高)

如果都通过的话,就广播PREPARE消息给其他节点,表示自己收到并认可[n,v]这个请求,进入prepare阶段。如果没有通过,则忽略该消息。

这里想一个问题,从节点能不能收到PRE-PREPARE消息就执行请求呢?答案显然是不能的,因为不能确认本节点与其他节点收到的是相同的请求消息,此时不能确定主节点是不是正常节点,如果主节点是恶意节点呢?比如,发送给从节点1的消息是m,而发送给从节点2的消息是m',如果直接执行就会出现从节点的不一致。因为不能确认本节点与其他节点收到的是相同的请求消息,所以要通过从节点与从节点交互的方式互相告知收到了请求消息,好让后面阶段对比一下,是否一致。

PREPARE:
收到PREPARE消息后,进行如下处理:

  • 消息合法性检查,消息签名是否正确,消息摘要是否正确。
  • 视图检查,检查是否是同一个视图号v
  • 水线检查,判断n是否在hH之间。

如果上面都通过,就将PREPARE消息加入到日志中,并继续收集PREPARE消息,如果收到正确的2f 2f 张(包括自己)PREPARE消息,这里如何验证是否正确呢?主要是收到的PREPARE要与PRE-PREPARE中的vnd等信息要匹配,就进入COMMIT阶段,广播COMMIT消息

这一阶段一般也可以称为第一轮投票,目的是什么呢?论文中是这么说的:The pre-prepare and prepare phases of the algorithm guarantee that non-faulty replicas agree on a total order for the requests within a view. 浓缩为两个字就是定序,确定在同一视图下足额的正常的节点都对来自客户端的请求有相同的定序。再说的直白点,就是解决上面提到的,无法确认本节点与其他节点收到的消息是否一致的问题。通过检查相同视图号v及同一序号n下的消息摘要d是否一致来判断同一视图配置下的同一个序号请求的消息是否一致。同时也确保了有足够数量的节点收到了一致的消息请求。

可以再想一个问题,此时可以直接执行请求吗?答案是不可以,因为此时,你只能确认自己收到了2f 2f 个一致的PREPARE消息,你无法确认其他节点是否也收到了2f 2f 个一致的PREPARE消息。也就是说,当前,你只能确认自己准备好了去执行序号为n的请求,但是你不能确认其他节点有没有准备好,所以,还要再进行一次节点间的消息交互,互相告诉大家,我准备好了。

COMMIT:

在上一阶段,节点收到足额PREPARE投票后会广播COMMIT投票,过程类似,当节点收到其他节点的COMMIT投票消息后,会进行如下检查:

  • 消息合法性检查,检查消息签名是否正确,消息摘要正不正确有没有被篡改。
  • 视图检查,view是否匹配。
  • 水线检查,判断n是否在hH之间。

如果都通过则把收到的投票消息写入日志log中,如果收到的合法的COMMIT投票消息大于等于2f+1 2f+1 个(包括自己),意思就是,已经确认大多数节点都准备好了执行请求,就执行请求并回复REPLY消息给客户端。这里如同上面一样,也是检查视图,序号及消息是否匹配。

REPLY:

客户端收到REPLY后,会进行统计,如果收到f+1 f+1 个相同时间戳t和响应值r,则认为请求响应成功。如果在规定的时间内没有收到回应或者没有收到足额回应怎么办?可以将该请求广播给所有节点,节点收到请求后,如果该请求已经被状态机执行了,则再次回复客户端REPLY消息,如果没有被状态机执行,如果节点不是主节点,就将该请求转发给主节点。如果主节点没有正常的将该请求广播给其他节点,则将会被怀疑是主节点故障或恶意节点,当有足够的节点都怀疑时将会触发视图变更协议,更换视图。

我们进行进一步的分析,可以看到,如果是客户端没有收到任何回应,很有可能是主节点故障或主节点是恶意节点(我就故意不执行你的请求),没有将请求足额广播给其他节点,(当然还有消息丢失等原因,这里不在详细分析),这时,客户端因一直没有响应,所以将请求广播给了所有节点,所有节点收到请求后,转发给主节点后发现主节点怎么什么都不干呀,怀疑主节点有问题,然后触发视图更换协议,换掉主节点。当然,客户端没有收到足额回应的一个原因还可能是消息丢失,那么如果是已经执行了该请求的节点再次收到该请求后会再次回应REPLY,前提是该请求是在水线范围内的合法请求,否则被拒绝。

检查点协议

在上面的一致性协议中可以看到,系统每执行一个请求,服务器都需要记录日志(包括,request、pre-prepare、prepare、commit等消息)。如果日志得不到及时的清理,就会导致系统资源被大量的日志所占用,影响系统性能及可用性。另一方面,由于拜占庭节点的存在,一致性协议并不能保证每一台服务器都执行了相同的请求,所以,不同服务器状态可能不一致。例如,某些服务器可能由于网络延时导致从某个序号开始之后的请求都没有执行。因此,设置周期性的检查点协议,将系统中的服务器同步到某一个相同的状态。简言之,主要作用有2个:1、同步服务器的状态;2、定期清理日志。

同步服务器的状态,比较容易理解与做到。比如在区块链系统中,同步服务器的状态,实际上就是追块,即服务器节点会通过链定时广播的链世界状态或其他消息获知到自己区块落后了,然后启动追块流程。

定期清理日志,怎么做呢?首先要明确哪些日志可以被清理,哪些日志仍然需要保留。如果一个请求已经被f+1 f+1 台非拜占庭节点执行,并且某一服务器节点i可以向其他服务器节点证明这一点,那么该i节点就可以将关于这个请求的日志删除。协议一般采用的方式是服务器节点每执行一定数量的请求就将自己的状态发送给所有服务器并且执行一个该协议,如果某台服务器节点收到2f+1 2f+1 台服务器节点的状态,那么其中一致的部分就是至少有f+1 f+1 台非拜占庭服务器节点经历过的状态,因此,这部分的日志就可以删除,同时更新为较新状态。

具体实现时可以联想到上面的一致性协议总的水线检查。上面的低水线h值等同于稳定检查点,稳定检查点之前的日志都可被清理掉。高水线H=h+k,也就是接收请求序号上限值,因为稳定检查点往往是间隔很多的序号才触发一次,所以k一般要设置的足够大。例如,每间隔100个请求就触发一次检查点协议,提升水线,k可以设置为200。

这里解释一下稳定检查点的概念,可以理解为当2f+1 2f+1 个节点都达到了某个请求序号,该请求序号就是稳定检查点。所有稳定检查点之前的消息都可以被丢弃,减少资源占用。 对比Raft,Raft是通过快照的方式压缩日志,都需要一个清理日志的机制,不然日志无限增长下去会造成系统不可用

视图更换协议

在一致性协议里,已经知道主节点在整个系统中拥有序号分配,请求转发等核心能力,支配着这个系统的运行行为。然而一旦主节点自身发生错误,就可能导致从节点接收到具有相同序号的不同请求,或者同一个请求被分配多个序号等问题,这将直接导致请求不能被正确执行。视图更换协议的作用就是在主节点不能继续履行职责时,将其用一个从节点替换掉,并且保证已经被非拜占庭服务器执行的请求不会被篡改。即,核心有2点:1,主节点故障时,可能造成系统不可用,要更换主节点;2,当主节点是恶意节点时,要更换为诚实节点,不能让作恶节点作为主节点。

当检测到主节点故障或为恶意节点触发视图更换时,下一任主节点应该选谁呢?PBFT的办法是采用“轮流上岗”的方式,通过(v+1) mod N (v+1) mod N ,其中v v 为当前视图号,N N 为节点总数,通过这一方式确定下一个视图的主节点。还有个更关键的问题,什么时候触发视图更换协议呢?我们继续往下讨论。

如果是主节点故障的情况,这种情况一般较好处理。具体实现时,一般从节点都会维护一个定时器,如果长时间没有收到来自主节点的消息,就会认为主节点发生故障。此时可触发视图更换协议,当然具体实现时,细节可能会不同,比如,也可以是这种情况,客户端发送请求给故障主节点必然导致长时间收不到响应,所以,客户端将请求发送给了系统中所有从节点,从节点将请求转发给主节点并启动定时器,如果主节点长时间没有将该请求分配序号发送PRE-PREPARE消息,认为主节点故障,触发视图更换协议。这2种情况比较好理解,但就这2种情况吗?其实还有以下几种情况也会触发视图更换协议:

  • 从节点广播PREPARE消息后,在约定的时间内未收到来自其他节点的2f 2f 个一致合法消息。
  • 从节点广播COMMIT消息后,在约定的时间内未收到来自其他节点的2f 2f 个一致合法消息。
  • 从节点收到异常消息,比如视图、序号一致,但消息不一致。
    这三点,都有可能是主节点作恶导致的,但也有可能是消息丢失等原因导致的。虽然不一定是因为主节点异常导致的,但从另一个角度看,解决了从节点不能无限等待其他节点投票消息的问题。

这里补充一点,触发视图更换协议后,将不再接收除检查点消息、VIEW-CHANGE消息、NEW-VIEW消息之外的消息。也就是视图更换期间,不再接收客户端请求,暂停服务。

解决了什么时候触发的问题后,下一个问题就是具体怎么实现呢?当因上面的情况触发视图更换协议时,从节点i就会广播一个VIEW-CHANGE消息,序号n是节点i的最新稳定检查点sC是2f+1 2f+1 个有效检查点消息,是为了证明稳定检查点s的正确性,P是位于序号n之后的一系列消息的结合,这里要包含这些信息可以理解为是证据,也就是说,从节点不能随便就发送一个VIEW-CHANGE,什么证据都没有,别人怎么能认同你更换视图呢?。上面我们提到过下一任主节点是谁的问题?通过(v+1) mod N (v+1) mod N 确定的一下任主节点p(在图中就是节点1),在收到2f 2f 个有效的VIEW-CHANGE消息后,就广播消息,这里VO具体的生成

你可能感兴趣的:(瓴彩釒)