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

当时有两个领导者

日志会无限增长

在 TT Lab 中继续学习

一句话总结

如果把达成共识的顺序全部保留,重启时间和磁盘就会随日志长度成比例增长。快照 把“应用到这里的结果”存成一份,使它前面的日志可以丢弃,而代价是 恢复流程和备份的含义都变了。

为什么需要它

共识算法并不保存值,它保存的是命令的顺序。复制状态机的前提是: 把这个顺序从头再应用一遍,总会得到同样的状态, raft.github.io 把这一点解释为“共识算法用来对服务器日志中的命令达成一致” (raft.github.io)。

问题在于这份日志永远不会缩短。每秒接收 100 条的集群,一天 要积累 860 万个条目。比磁盘被占满更痛的是重启时间。 如果必须把日志从头再应用一遍,重启一个节点可能要花上好几天。 追赶也是如此。在加入新成员,或者让长时间宕机的节点恢复时,如果领导者 需要发送的条目有几百万个,这种恢复实际上是不可能的。

工作原理

解法很简单。把应用到某个时间点的结果整体存下来,然后丢弃它前面的日志。 存下来的内容里,要同时写明最后包含的位置编号和那个位置的任期。有了这两样, 才能把它后面的日志接上去。

  버린다                         남긴다
 [1..20000 로그]  →  스냅샷(last_index=20000, last_term=7) + [20001.. 로그]

这里会产生一个新问题。领导者想把第 20001 号条目发给追随者,而这个追随者 只有到 19000 号为止,那么领导者手里已经没有可以发送的日志了。所以实际实现会另外 设置一条直接传送快照本身的路径。当落后的程度超出了可以用日志补齐 的范围,就把整份快照发过去,从那之后再用日志接续。

在 etcd 中,这个旋钮是 --snapshot-count。当内存中持有的 raft 条目达到这个数量时就会 发生压缩,默认值在 v3.2 中从 10,000 改成了 100,000。清理键的 历史修订版本,是另一个旋钮——自动压缩,由 --auto-compaction-mode 和 --auto-compaction-retention 来设定。碎片整理 (etcdctl defrag)又是另一回事,文档警告说:“对正在运行的成员做碎片整理,在重建状态期间, 读写会被阻塞” (etcd 维护)。

备份的含义也变了。etcd 保存快照用 etcdctl snapshot save,恢复用 etcdutl snapshot restore。重要的是,恢复并不是让原来的集群复活。 文档写道:“恢复会覆盖快照元数据的一部分(成员 ID 和集群 ID),该成员会失去先前的身份”, 因此又说:“要用快照启动集群,就必须启动一个新的逻辑集群” (etcd 灾难恢复)。

把三个名字相似的东西区分开,在运维中就不会混淆。日志压缩是缩减 raft 日志, 自动压缩是缩减键的历史修订版本,碎片整理则是把这样腾空的 位置归还给磁盘。前两个无论怎么运行,文件大小都不会缩小, 碎片整理无论怎么运行,修订版本也不会减少。

在现场相遇的样子

最常见的事故,是备份做了却一次也没有做过恢复。上面这句话 的含义是,恢复并不是“回到昨天的状态”,而是建立一个新集群。 成员 ID 会变,所以不能让已有的成员混进来,所有成员都必须用同一份 快照来恢复。如果到故障当天才第一次读这个流程,就已经晚了。

第二种是快照时间点与实际数据之间的间隔。文档警告说,如果从 member/snap/db 文件 做快照,“可能会丢失尚未被记录、但存在于 wal 文件夹中的数据”。 所以备份不是用复制文件的方式,而是用 snapshot save 来做。

第三种是压缩与备份周期的关系。日志压缩得越频繁,重启就越快, 但制作快照本身是有成本的,每次都会让响应短暂变慢。反过来,如果压缩得 很少,平时很安静,而到重启时要一次性付出代价。哪种更好,取决于 “多久重启一次”,而不是由文档的默认值来决定。

下一项测验要确认什么

确认快照丢弃什么、保留什么,为什么必须同时写明最后的位置编号和任期, 日志压缩、修订版本自动压缩和碎片整理是三件不同的事,以及恢复为什么 是建立新集群。