TT Lab
开始
学习 学习路径 课程

当时有两个领导者

收到与提交并不相同

在 TT Lab 中继续学习

一句话总结

领导者收到值并写进自己的日志,这件事本身什么都不能保证。只有当过半数节点在同一位置拥有 同一个条目,并且该条目属于领导者的当前任期时,它才算“提交”,而到那时才 能对客户端说成功。

为什么需要它

选举结束后,接收写入的地方就确定为一个。但是这一个节点收到的值, 要怎样传播给其余节点呢?如果只是“发出去就忘了”,那么领导者刚一收到值就崩溃时, 这个值在任何地方都不存在。反过来,如果“等所有节点都收到”,那么只要有一台很慢, 整体就会停摆。共识选中的,是介于两者之间的过半数。

选择过半数,原因不是性能,而是安全。两个过半数必然有重叠,所以只要某个 条目已经到达过半数,在可能成为下一任领导者的候选人当中,至少有一个拥有该条目。 再加上一个条件——只有候选人的日志至少与投票者的一样新时, 才投票——就只有拥有该条目的节点才能成为领导者。所以已提交的条目 会保留给任何未来的领导者。

工作原理

日志是只追加的列表,每个条目都附带当时的任期。这个任期 是比较两份日志的唯一依据。

index :   1        2        3        4
term  :   1        1        2        3
value : "a"      "b"      "c"      "d"

领导者向追随者发送条目时,会说“如果你的日志第 2 个位置的任期是 1,就把这些 接在它后面”。追随者只确认前面那个位置。之所以不比较整体,是因为 日志匹配——规则中自然推出这样的性质:只要同一位置上有相同任期的条目,它前面的全部内容都相同。 如果前面的位置对不上,追随者就拒绝,领导者则往回退一格 再问一次。找到吻合的位置之后,就从那里开始用自己的内容填充。

提交由领导者来数。统计每个追随者已经收到了哪里,把过半数节点拥有的 最高位置作为提交索引(commit index)。这里还附带着一个几乎总是被漏掉的附加条件。

리더는 자기 임기의 항목이 과반에 닿았을 때만 커밋 번호를 올린다.
앞 임기의 항목은 그 자체로는 과반에 닿아도 커밋하지 않는다.

该代码块中的韩文注释依次说明:领导者只有在自己任期的条目达到过半数时,才会提高提交索引;前面任期的条目,即使本身已达到过半数,也不提交。

缺了这个附加条件,就会发生这样的事。前一个任期的条目被复制到过半数并提交,而那个领导者 崩溃后,另一个没有该条目的节点(因为它拥有更高任期的条目)成了领导者, 就会用别的条目覆盖那个位置。已经宣布确定的值就这样消失了。论文 用图 8 来说明这种情形,并加入上面的附加条件作为解法。新领导者只要提交了 自己任期的任何一个条目,它前面的那些条目也就一并安全了。

领导者还为每个追随者分别保存着另一个值:“下次要从哪里开始发送给这个追随者”。 新当选的领导者会乐观地把这个值设为自己日志的末尾, 每被拒绝一次就减一格。大多数追随者一两次就能找到吻合的位置, 所以这份乐观平时是免费的。代价是,对于长时间失联的节点,回退会变得很长, 这就是需要快照的原因。

在现场相遇的样子

这一区分会改变客户端所收到响应的含义。如果 etcd 的写入返回了成功, 那个值就已经到达了过半数,所以即使领导者在那之后马上崩溃,它也能存活下来。反过来,如果响应是 timeout,那么这次写入可能成功了,也可能失败了——因为也许是到达过半数之后 只有响应丢失了。所以使用分布式存储的代码,必须让重试具备幂等性。 etcd 文档把事务和修订版本(revision)放在前面,原因就在这里 (etcd——为什么是 etcd)。

追赶也是每天都会看到的场景。追随者之一重启后,领导者会把发送的位置编号 一格一格地往回退,寻找吻合的位置。日志很长时,这种回退要花很久,所以 实际实现里会一起使用快照。etcd 在日志积累到一定数量时进行压缩,决定这个数量的 --snapshot-count 的默认值,在 v3.2 中从 10,000 改成了 100,000 (etcd 维护文档)。

第三种经常见到的是“一台慢追随者”。只要有过半数,提交就能推进, 所以一台慢的不会阻塞服务。正因如此它并不显眼,直到某一天另一台 重启时才暴露出来——因为在那一刻,能组成过半数的健康节点只剩一个了。 复制延迟应当被解读为冗余被消耗,而不是故障。

下一项实验要做什么

亲手编写四条日志规则——追加、日志匹配检查、从对不上的位置开始截断, 以及提交索引的计算。最后两步,向三个节点写入值,观察提交如何扩散, 并让一个追随者停掉再恢复,亲自确认它从空日志开始追赶的过程。