上一篇 Lab2A: 选主 中介绍了Raft选主的实现,本篇介绍Raft日志复制的原理和实现。实验二共包含四个子实验:
本文是第二个子实验,需要实现Raft日志复制。由于Raft的强领导人特性,命令都是通过Leader顺序追回到日志中,然后通过Leader复制到各Follower中,由于Raft的日志安全特性,使得集群节点中的日志最终达到一致。在开始实验前,先阅读以下材料:
- 阅读论文:In Search of an Understandable Consensus Algorithm,以下简称论文。
- 阅读实验指导:6.824 Lab 2: Raft Part2B
- 阅读Students’ Guide to Raft,本文中介绍了些论文中没有给出的实现细节。
Raft日志复制是Raft协议最核心的部分,也是最难的部分。论文中将日志复制和安全性是分开讨论的,实现的时候我们需要将这两部分结合起来。另外论文中对一些实现细节是空白的,如日志冲突的优化,这要求我们要实现的时候要考虑各种边界问题。
基本原理
选主成功后,Leader就开始接收客户端的请求。每个请求是能够被复制状态机执行的命令(command)。Leader首先将命令作为一个日志记录(entry)追回到日志中,然后并行地向集群中的其它节点发起AppendEntries RPC请求。当日志记录被安全复制到其它节点后,Leader将该记录应用到状态机中,并将状态机执行的结果返回给客户端。如果Follower崩溃或网络丢包,Leader会不断重试向Follower发送AppendEntries RPC,走到Follower最终全部保存了Leader的日志记录。
以上就是Raft日志复制的全部流程,它涉及到很多细节,这里我主要说一下日志记录和安全性。
日志可以理解成是一个包含日志记录(entry)的列表,每个日志记录包含了三个信息:
- Term:Leader生成日志记录时任期号
- Index:日志记录在日志中的索引号
- Command:可以被状态机执行的命令
Term和Index主要用于日志一致冲突检测(下面会介绍);Command是上层状态机的一个概念,Raft其实并不理解,只是Raft需要解决Command的一致性问题,所以Raft把Command当成一个上下文参数存储到一个日志记录中,当Raft认为一个日志记录在集群中已经达成一致后,Raft只是将这个Command再较交给状态机去执行。这个地方说的达成一致,就是指日志已经被安全复制。
在讨论什么叫安全复制之前,先直接抛出Raft给出的安全性保证,这些保证在论文中的详细论述和证明,在此不一一展开