Raft将共识算法分离出了三个关键点
- leader election
- log replication
- safety
raft使用了共识来减少必须要考虑的状态的数量。降低了不确定性的程度和服务器之间的不一致
raft具有的新特性
- Strong leader (强领导性):raft使用了更强的领导形式,比如日志条目只能从leader流向follower。这使得raft更加易懂同时简化了日志复制的管理流程。
- leader election(领导选举):
- membership changes(成员变更):
共识算法的主要工作就是保证复制日志的一致性
实验任务
实现 Raft 协议的领导者选举和心跳(不包含日志条目的 AppendEntries RPC)。Part 2A 的目标是选举单一领导者,如果没有故障,则保持领导者的地位,并且在旧领导者失败或与旧领导者之间的数据包丢失时,新领导者接管。运行 go test -run 2A 以测试您的 2A 代码
代码
实现Raft算法时,您需要修改raft/raft.go文件。该文件包含骨架代码以及如何发送和接收Raft共识算法中的RPC的示例。
您的实现应该支持raft.go中提供的接口。这个接口将被测试器和最终您的键/值服务器使用。请确保查阅raft.go中的注释,以获取有关如何实现和使用接口的更多详细信息。 如果您需要关于实现的特定部分的帮助,请随时提供具体的代码片段或问题。
一个服务调用Make(peers, me, ...)来创建一个Raft对等节点。peers参数是一个Raft对等节点的网络标识符数组(包括本节点),用于RPC。me参数是这个节点在peers数组中的索引。Start(command)要求Raft开始处理将命令附加到复制日志的过程。Start()应该立即返回,而不等待日志附加完成。服务期望您的实现为每个新提交的日志条目向Make()的applyCh通道参数发送一个ApplyMsg。
raft.go包含示例代码,演示了如何发送RPC(sendRequestVote())以及如何处理传入的RPC(RequestVote())。您的Raft对等节点应该使用labrpc Go包(源代码在src/labrpc中)交换RPC。测试器可以告诉labrpc延迟RPC、重新排序它们,并丢弃它们以模拟各种网络故障。虽然您可以暂时修改labrpc,但请确保您的Raft与原始的labrpc一起工作,因为这是我们用于测试和评分实验的工具。您的Raft实例只能使用RPC进行交互;例如,它们不允许使用共享的Go变量或文件进行通信。
后续的实验建立在这个实验的基础上,因此给自己足够的时间编写可靠的代码是很重要的。
提示
无法直接运行 Raft 实现,而是应通过测试器运行,即 go test -run 2A。 遵循论文中的图2。在这一阶段,您关心发送和接收 RequestVote RPC、与选举相关的服务器规则,以及与领导者选举相关的状态。 在 raft.go 中的 Raft 结构体中添加用于领导者选举的图2状态。您还需要定义一个结构体来保存有关每个日志条目的信息。 填写 RequestVoteArgs 和 RequestVoteReply 结构体。修改 Make(),创建一个后台 goroutine,定期通过发送 RequestVote RPC 启动领导者选举,当它有一段时间没有收到其他对等方的消息时。这样一来,对等方将学到谁是领导者(如果已经有领导者),或者成为领导者本身。实现 RequestVote() RPC 处理程序,以便服务器相互投票。 为了实现心跳,定义一个 AppendEntries RPC 结构体(尽管您可能还不需要所有参数),并让领导者定期发送它们。编写一个 AppendEntries RPC 处理方法,它会重置选举超时,以便其他服务器不会在已经选举了领导者的情况下自行成为领导者。 确保不同对等方的选举超时不总是同时触发,否则所有对等方将只为自己投票,没有人会成为领导者。 测试器要求领导者每秒不超过十次发送心跳 RPC。 测试器要求在旧领导者失败后的五秒内选举新领导者(如果大多数对等方仍然可以通信)。但请记住,领导者选举可能需要多个轮次,以防发生分裂投票(如果数据包丢失或候选者不幸选择相同的随机退避时间)。您必须选择选举超时(因此心跳间隔)足够短,以确保即使需要多个轮次,选举也很可能在五秒内完成。 论文的第5.2节提到选举超时在150至300毫秒的范围内。只有在领导者发送心跳频率远远高于每150毫秒一次时,这样的范围才有意义。由于测试器限制每秒心跳不超过10次,因此您将不得不使用大于论文中150到300毫秒的选举超时,但也不能太大,否则可能无法在五秒内选举出领导者。 您可能会发现 Go 的 rand 包很有用。 您需要编写定期或延迟时间后采取行动的代码。最简单的方法是创建一个带有循环的 goroutine,其中调用 time.Sleep()(参见 Make() 为此目的创建的 ticker() goroutine)。不要使用 Go 的 time.Timer 或 time.Ticker,它们很难正确使用。 Guidance 页面提供了有关如何开发和调试代码的一些建议。 如果您的代码在测试中遇到问题,请再次阅读论文中的图2;领导者选举的完整逻辑分布在图的多个部分。 不要忘记实现 GetState()。 测试器在永久关闭实例时调用 rf.Kill()。您可以使用 rf.killed() 检查是否调用了 Kill()。最好在所有循环中执行此操作,以避免死掉的 Raft 实例打印混乱的消息。 Go RPC 仅发送名称以大写字母开头的结构字段。子结构也必须具有大写字母开头的字段名称(例如,数组中的日志记录字段)。labgob 包会警告您此事项;不要忽略这些警告。
测试标准
每个 "Passed" 行包含五个数字;它们分别是测试所花费的时间(以秒为单位)、Raft 对等方的数量、测试期间发送的 RPC 数量、RPC 消息中的总字节数,以及 Raft 报告已提交的日志条目的数量。您的数字将与此处显示的数字不同。如果愿意,您可以忽略这些数字,但它们可能有助于您检查您的实现发送的 RPC 数量是否合理。对于实验2、3和4,如果所有测试(go test)的总时间超过600秒,或者任何单个测试花费超过120秒,评分脚本将失败您的解决方案。
在评估您的提交时,我们将在没有 -race 标志的情况下运行测试,但您还应确保您的代码使用 -race 标志一致通过测试。
2A 的测试函数
TestInitialElection2A(t *testing.T):测试能否正确选出leader并且维持leader的领导地位
TestReElection2A(t *testing.T):测试能否在领导人失效或网络故障情况下能够正确进行重新选举
TestManyElections2A(t *testing.T):测试能否在多次网络故障和恢复的情况下,能够正确进行领导人的选举