尝试着论证下使用阻塞日志场景下,成员变更的正确性(支持变更少数派个成员,不能同时进行上线和下线两个操作):
1)备机slave收到[Cold,Cnew]的确认条件是要求之前的日志都已经收到,这样保证如果[Cold, Cnew]日志得到Cold, Cnew两个集群的多数派应答,那么[Cold, Cnew]之前的日志都已经在Cnew上形成了多数派;
2)[Cold, Cnew]和[Cnew]两条日志都是阻塞日志,即集群工作状态所有的成员变更日志都是阻塞日志。这里阻塞日志的概念和日照邮件中指出的是一致的,即备机对收到的比[Cold, Cnew](或[Cnew]) logID大的日志的确认条件是,备机已收到[Cold, Cnew](或[Cnew])日志,如果未收到成员变更日志,备机不会对后续的日志做确认(可以先缓存起来,避免再向主机要)。阻塞日志的实现见后,这样保证了[Cold, Cnew]之前,[Cold, Cnew]到[Cnew]之间,[Cnew]之后三个区间的日志对集群的认知都是一致的,其取得多数派的条件也是一致的,不会出现不同server在宕机重启恢复时,对不同日志所处集群认知不一致的情况。 这样就和raft的成员变更阶段保持一致了。
3)阻塞日志的实现: 每条日志都有一个专门的状态,我们称作为mc_term (membership change term),这个值初始是0,leader在发送每个成员变更日志时递增这个mc_term的值,在后续的日志中都带新值。 备机会在本地的内存中缓存这个值,备机收到每条日志都会检查这个值和自己保存的值,如果收到的值比保存的值大,表示这个日志之前有副本变更日志没有收到,则不会对此日志做确认。备机会在收到成员变更日志并落盘,且成员变更日志和本地保存的mc_term值只差一(避免收到的成员变更日志不连续)会更新本地缓存的这个mc_term值。由于备机写到磁盘的日志都是可以被确认的,宕机恢复时,server只需要检查自己保存的logid最大的日志即可获知恢复后这个值是多少,无需再额外保存副本变更日志做处理;
4)选举条件保持和raft一致,取最后一条logid的termID和logID。
5)副本变更日志为阻塞日志不会影响可用性,会稍微影响乱序的性能(实际上只在[Cold,Cnew]和[Cnew]这两个点后续的日志要求一定的顺序性),leader仍是要求收到每一条的多数派即可提交。每个server(无论主备)保存的成员变更日志都是全的。
6)在投票时要求核对选举组,及[Cold]可以给[Cold]/[Cold,Cnew]投票,[Cold, Cnew]可以给[Cold,Cnew]/[Cnew]投票,反之不成立。这实际上一方面保证选举组有效,另一方面保证选出的leader都在更新的选举组里。对应到实现上,及一台Server只给比自己mc_term大于等于的server投票,不会给比自己mc_term小的server投票。
阻塞日志的概念同样适用于freeze和checkpoint的实现。