共识算法:Multi Paxos

200 阅读7分钟

Multi-Paxos

Basic Paxos决议出一个提案值,而Multi-Paxos决议出多个提案值 一个Paxos实例用来决议出一个值,多个Paxos实例是可以

确定日志索引

日志中包含多个日志条目,如果通过Paxos不断确认一条条日志的值,那么需要知道当次Paxos实例在写日志 的第几位。因此,Multi-Paxos做的第一个调整就是要添加一个关于日志索引的index参数到Basic Paxos的第 一阶段和第二阶段,用来表示某一轮Paxos用于决策哪一个日志条目 增加索引后的流程如下:

  1. 找到第一个没有被批准的日志条目的索引,记为index
  2. 运行Basic Paxos算法,对index位置的日志用客户端请求的提案值进行提案
  3. 第一阶段接受者返回的响应是否包含已接受的值acceptedValue?如果已有接受的值,则用acceptedValue 作为本轮Paxos提案值运行,然后回到步骤1继续寻找下一个未批准的日志条目索引;否则继续运行Paxos算法, 继续尝试批准客户端的提案值

三个节点的系统可以容忍一个节点出现故障,假设此时S3已宕机,当S1收到客户端最新的请求命令jmp时,流程 如下:

  1. 服务器S1找到第一个还未被批准的日志条目,也就是index为3的cmp
  2. S1的提议者尝试将jmp作为第3位日志的命令,运行Paxos算法
  3. 因为S1已经接受了cmp,所以S1的接受者会在第一阶段中响应已接受的提案值cmp;那么S1的提议者将cmp作为 提案值进行第二阶段Accept;S2因index 3空置,也会接受cmp,因此满足半数机制,变为批准状态;然后S1继续 寻找下一个没有批准的日志条目的位置,也就是第4位
  4. S2的第4位接受了sub,所以S2会在第一阶段中响应sub;同第三步一样,S1在第二阶段将提案值替换为sub, 满足半数机制;S1继续找下一个未被批准的日志,第5位
  5. S1和S2的第5位都为空,S3第5位位cmp,但S3已宕机;所以S1和S2都在第5位接受了jmp,jmp被批准,S1向 客户端返回成功消息

image.png

通过上面的流程可以发现,有时候提议者需要重试很多次才能成功批准一条命令,比如上面提交cmp、sub 之后,才最终完成了jmp的批准 针对提案冲突和消息轮次过多的问题,Multi-Paxos通过以下两个方式优化

  1. 领导者选举,从多个提议者中选举出一个领导者;任意时刻只有领导者来提交提案,这样可以避免提案冲突; 若领导者故障,则重新选举一个,避免单点故障
  2. 减少第一阶段的请求,有了领导者之后,可以保证提案编号都是单调递增的;因此只需要对整个日志发送 一次第一阶段请求,后续就可以直接通过第二阶段来发送提案值

领导者选举

让服务器id最大的成为提议领导者,具体流程如下

  1. 让server_id最大的服务器成为领导者,这意味着每台服务器需要知道其他服务器的server_id
  2. 每个节点每隔T ms向其他服务器发送心跳信息,彼此交换自己的server_id
  3. 如果一个节点在2T ms内没有收到比自己server_id更大的心跳信息,那么它就成为领导者;该服务器需要 处理客户端请求,并且同时担任提议者和接受者
  4. 如果一个节点收到了比自己server_id更大的心跳信息,那么它不能成为领导者;该节点会拒绝客户端的请 求,或者将客户端请求重定向到领导者,并且该节点只会担任接受者

这种算法下,系统出现脑裂的概率也很小,即使出现了,也只会退回到多提议者的状态,算法仍可以正常工作 存在的问题:领导者的日志落后于其他节点,所以可能需要先补齐领导者日志,很可能造成服务停止响应

减少请求

Paxos第一阶段主要有两个作用

  1. 屏蔽过期的提案(接受者会比较提案id是否是自己见过最大的);但由于每个日志条目的Paxos实例是互相 独立的,所以每次请求只能屏蔽一个日志条目的提案(id+1的提案下次还是会再经过一轮判断)
  2. 用已经接受的提案值代替原本的提案值 对于第一点,让提案编号变成全局的,整个日志使用同一个单调递增的提案编号;一旦第一阶段的请求响应成 功,整个日志的第一阶段的请求都会阻塞,第二阶段正常将相关提案写到对应索引上 对于第二点,需要增加第一阶段响应返回的信息,用于表示接受者没有要返回的已接收的提案,这意味这领导 者可以直接发起第二阶段Accept的请求;接受者发送noMoreAccepted,如果该参数为true,则表示后面没有 需要决议的提案;如果领导者收到了超过半数的接受者回复了noMoreAccepted为true,领导者就能直接发送 第二阶段请求,后续客户端的请求只需要一轮消息传递就能完成

副本的完整性

需要每台机器的日志都是完整的,知道哪些日志是被批准的,这样状态机才能安全的执行日志,达到一致的状态 第一个优化,领导者在接收到半数以上的接受者响应后,可以继续处理后续请求,同时在后台对未响应的接受者 进行重试,尝试将提案值复制给所有接受者 第二个优化,增加以下两个变量来记录哪些日志是被批准的

  1. acceptedProposal数组,acceptedProposal[i]代表第i条日志的提案编号,如果其被批准,则acceptedProposal[i] 等于无穷大;无穷大则表示该位置的日志不会再接受其他提案值,也就是该提案已被批准
  2. 每个节点都维护一个firstUnChosenIndex变量,表示第一个没有被批准的日志位置,即第一个acceptedProposal里 不等于无穷大的位置;领导者在发送第二阶段的Accept消息的时候带上变量firstUnChosenIndex,这样接受者在收到 Accept消息的时候进行判断,如果第i条日志满足i < firstUnChosenIndex且提案编号相等,则认为第i条日志是被批准 的日志,会将其值设置为无穷大

image.png

客户端请求

客户端请求的顺序决定了状态机执行命令的顺序 首先,客户端第一次发起请求时,并不知道系统中谁是领导者,客户端可以任意请求一台服务器,如果该服务器不是领导 者,则将请求重定向给领导者 之后客户端会一直和领导者交互,直到领导者宕机或异常;客户端会请求其他服务器,然后重新选举领导者

一条日志被批准且被领导者的状态机执行之后,才能返回给客户端

如果领导者在日志批准且执行完成后宕机,未发送响应给客户端;客户端会重试该请求,造成该命令被执行两次;这个问 题的解决方法是,客户端为每次请求都带上一个唯一id,服务器将该id和命令一起保存在日志中;状态机在批准一条日志 之前,先检查其对应的id是否被执行过,若执行过,则直接发送成功响应给客户端