上一篇博客《RAFT中成员变更过程以及失败回滚分析 》,分析了副本变更的背景知识和RAFT提出的joint consensus的实现方式。可惜的是,RAFT在其论文和博士论文中都没有给出joint consensus的严格证明,我们在实现oceanbase的乱序日志方案时,初期参照RAFT的joint consensus,但总能找到协议漏洞,于是采用 case by case的方式逐步修改协议,直到我们几个人再 也找不到漏洞的时候,还是不能证明方案设计的正确性。
忘了哪位Google的大神说过“世界上只存在一种一致性协议,那就是PAXOS”,好吧,为了正确性,还是要从伟大的PAXOS出发。于是,和同事讨论后,提出一种可以证明正确性的成员变更实现方案。
在没有成员变更的情况下,PAXOS决议的的类型只有一种,即某条日志的内容是什么(简称为L<Lid,session>);引入成员变更后,其实PAXOS决议的类型多了一种,我们之前一直没有引起重视,即某条日志的PAXOS group是哪些server(简称为G<Gid,Lid, group >)。显然,在提出(而不是批准)一个L的前,就需要确定L的G信息,这是保证正确性的必要条件。
如果我们假设外部有一个完全可靠的地方,能够提供可靠的读写服务,保证多次重复读,都能读到一个一致的信息,我们可以把G存储于此,每次在对L进行PAXOS propose的时候,去读取G信息,决定其所属的group。不幸的是,不存在这样的理想存储情况,我们必须利用现有的集群,把G信息存储多份,这又是一个PAXOS过程。
我们可以构造这样一个系统,满足以下约束:
1. 系统中只有一个leader,在上一条日志没有形成多数派之前,leader不会提出(propose)第二条日志,那么系统中无论发生了什么情况,最多只能有一个未决日志;
2. 系统在写第一条日志前,先写入一个初始配置信息S,指明第一条日志的PAXOS group 信息;
3. 每条日志除了包含原有日志内容外,还需要指明下一条日志的group信息(仅仅是下一条,而不是下一条到无穷大条),即第i条日志形成多数派后,第i+1条日志的PAXOSgroup 信息就不会变了,此时才可以进入第i + 1条日志的propose阶段。
显然,上述系统是正确的。这个显然,其实还是需要进一步证明。。。
在假设上述是正确的前提下,如果想实现成员变更,可以在第x条日志中,写入第x+1条的PAXOS group 信息,需要注意的是,此时x+1这条日志的内容是在new group的,而x+1的PAXOS group 信息是在old group上的,但无论是什么信息,都是满足一致性约束的。
这个系统并发度实在太低了,我们需要给他加速:
1. 系统初始配置信息S,指明了第1条到 第1K条日志的PAXOSgroup 信息;
2. 第1条日志的内容中,记录了第1001条日志的PAXOSgroup 信息,第2条日志的内容中,记录了第1002条日志的PAXOS group信息,也就是说第i 条日志中记录第i + 1K条日志的PAXOS group 信息。
在加速系统中,第1条日志之和第1K + 1条日志有先后关系,而第1条到1K条之间是独立的,是可以多条日志并发进行PAXOS 投票的,这也和我们之前的滑动窗口契合。
在加速系统中,如果需要做成员变更,假设当前滑动窗口的最右边的日志ID是right,则生成一条成员变更信息,信息内容是:从2K + right开始,日志需要写到新的group上,然后将这个信息写入第right + 1条日志中同步给多数派。第2K + right条日志的PAXOS group 信息是记录在1K + right日志内容中,因此,在1K + right日志生成(propose)前,第right + 1条日志必须形成多数派,这是一个约束。
这不同于raft ,必须经历一个joint consensus阶段,只需要一个阶段,即可以完成成员变更。
具体到实现中,可能还有各种各样的问题,需要进一步细化设计。