发现之前的Raft文章没有分析图8的问题,而这张图更容易引起歧义,在群里讨论过不止一次。在这里谈谈我的理解。图8用来解释为什么Leader不能提交上一任期的日志,只能通过提交自己任期的日志来间接提交上一任期的日志。先根据错误的情况,即Leader可以提交上任期的日志。那么上面的过程:(a)S1是term2的leader(仔细看,有黑框),日志已经复制到S2。(b)S1宕机,S5获得S3、S4、S5的选票成为leader,然后写入一条logindex=2&term=3。(c)S5crashesimmediatelyafterwriting,S1isre-electedasleader,currentTerm=4,nonewrequestscomeinatthismoment,S1copiesthelogwithindex=2&term=2toS3,amajorityisreached,和S1提交(注意term=2不是当前任期的日志,我们说的是错误的情况)。然后请求进来,刚写完localindex=3&term=4log,S1就失败了。(d)此时S5可以通过S2、S3、S4和自己的投票重新成为Leader(currentTerm>=5),并将index=2&&term=3的日志复制到所有其他节点并提交它。此时index=2的日志已经提交了两次!一次term=2,一次term=3,这是绝对不允许的,提交的日志不能被覆盖!(e)这里的情况是S1在shutdown之前将term=4的log复制到大部分机器上,这样S5就无法选举成功。这是S1正确复制而没有失败的情况。这里主要通过(c)和(d)来说明问题。其实这个图配合Raft纸的图会更容易理解。(d)和(e)分别对应term=4是否被复制到多数。因此,我们需要增加提交的约束来防止(d)的发生。这个约束是Leader只能为自己的任期提交日志。我们再来看看,添加约束后会发生什么?之前(a)和(b)没有变化,我们从(c)开始。(c)还是copyindex=2&term=2tomost,因为currentTerm=4,所以这个log不能提交。如果S1把term=4的日志复制给多数,那么Leader就可以提交日志了,index=2&term=2也会一起间接提交。其实就是(e)的情况,1-2-4都提交了。(d)中的情况我认为是理解问题的关键。如果S1只把term=4写入自己的log,然后down掉;S5成功选举成为leader,然后将index=2&term=3的日志复制到所有节点,现在index=2还没有提交,S5可以提交index=2&term=3的日志吗?答案是不。因为S5是由S1选举出来的(term=4),currentTerm至少是5,也有可能是6、7、8……我们假设是5,但是这个logterm=3,Leader不能提交上学期的日志,所以这个日志不能提交。只有当有新的请求进来,超过一半的节点复制1-3-5时,term=3的日志才能和term=5的日志一起提交。虽然这个约束不会重复提交,但是如果没有新的请求进来,index=2&term=3会不会一直提交?这里不是堵了吗?如果这是一个kv数据库,问题就很明显了。Assumethatthecommandinthelogwithindex=2in(c)or(d)isSet("k","1"),afterS5iselectedastheleader,theclientwillqueryGet("k"),而leader会发现log有记录但是不能回复1给client(因为log没有按照约束提交),线性一致性要求不能返回陈旧的数据,Leader急需知道是否可以提交此日志。所以raftpaper中提到引入no-oplog来解决这个问题。这是在etcd中实现的。引入空操作日志。no-oplog只有index和term信息,command信息为空。它也被写入磁盘存储。具体过程是当leader选举成功后立即添加一条no-oplog,并立即复制到其他节点。一旦提交了no-op日志,就间接提交了leader前面所有未提交的日志,问题解决。像上面的kv数据库,有了no-op日志,Leader可以快速响应客户端查询。no-oplog本质上是让Leader能够隐式快速提交上一任期未提交的日志,并确认当前的commitIndex,从而使系统能够快速对外正常工作。另外,6.824中的实验不需要实现无操作日志记录。这个问题之前被阿里巴巴团队称为“幽灵再现”,见《如何解决分布式系统中的“幽灵复现”?》,其中讨论了Paxos、Raft和Zab的解决方案。本文转载自微信公众号“多糖”,可通过以下二维码关注。转载本文请联系多歌堂公众号。
