在lab2A的基础上,实现日志复制和安全性
当新的leader被选举出来之后,新leader会通过AppendEntries来用作心跳和向其他followers发送变化的信息。
注意区分单个节点的commit和整个集群的applied。当leader收到客户端发送的日志后,会将这个日志通过appendEntries发送给各个follower,follower在成功接收到这个日志后会返回reply给leader一个success。当超过半数以上的节点返回成功接收到信息后,leader认为这个追加的日志被成功commit,并将其索引作为状态机(整个集群)成功commit的索引,即appliedIndex。
一开始,一个客户端发送了一个变化给leader,leader将这个变化记录在log中。
leader会在下一次心跳中携带这个信息,如果超过半数以上的followers确认了这个追加的日志信息,那么就认为这个信息被committed持久化了,然后leader就会发消息回去给这个客户端
Raft即使在有网络分区的情况下也能保持一致。
因为网络分区的存在,不同分区内的信息会不一致
当网络分区被修复后,任期小的节点(无论leader还是follower)在收到任期高的leader的心跳后,会将身份重置为follower,然后将log日志更新为新leader发来的log。这样集群的信息就得到了同步
代码部分
Start函数用来接收客户端发送来的请求。如果server不是leader则返回false;如果是leader则将command组装成LogEntry后追加到自己的日志中,并更新自己的matchIndex和nextIndex。
日志匹配性质
- 如果来自不同日志的两个日志项有相同的index和term,那么它们存储了相同的command
- 如果来自不同日志的两个日志项有相同的index和term,那么他们前面的日志完全相同。
- 只要PrevLogIndex和PrevLogTerm与follower的日志部分匹配,那么就能确保follower的PrevLogIndex前的日志一致
Leader在将日志项复制到多数派后更新commitIndex的同时,要调用sendApplyMsg()。Follower在AppendEntry RPC收到LeaderCommit的更新时,也要调用sendApplyMsg()。
在复制时要考虑什么情况下可以进行追加,什么情况下会conflict不能追加
- 如果prevLogIndex大于当前日志的最大下标,说明follower缺失日志,不能追加日志,要返回自己的缺失信息UpNextIndex给leader,让leader通过nextIndex来进行回滚
- 如果下标在prevlogIndex的日志的任期和prevLogTerm不相等,那么存在conflict。
lab2B最折腾人的是test函数里的下标是从1开始的,但是logs的下标是从0开始的。一开始写的时候,如果不注意统一,就会疯狂出错
- TestBasicAgree2B():基础的追加日志测试。先使用nCommitted()检查有多少的server认为日志已经提交(在执行Start()函数之前,所有的服务器都不应该提交日志),若满足条件则调用cfg.one(),其通过调用rf.Start(cmd)来追加日志。rf.Start(cmd)用于模拟Raft实例从Client接收实例的情况。
- TestRPCBytes2B:基于RPC的字节数检查保证每个cmd都只对每个peer发送一次。
- TestFailAgree2B:断连小部分,不影响整体Raft集群的情况检测追加日志。
- TestFailNoAgree2B:断连过半数节点,保证无日志可以正常追加。然后又重新恢复节点,检测追加日志情况。
- TestConcurrentStarts2B:模拟客户端并发发送多个命令
- TestRejoin2B:Leader 1断连,再让旧leader 1接受日志,再给新Leader 2发送日志,2断连,再重连旧Leader 1,提交日志,再让2重连,再提交日志。
- TestBackup2B:先给Leader 1发送日志,然后断连3个Follower(总共1Ledaer 4Follower),网络分区。提交大量命令给1。然后让leader 1和其Follower下线,之前的3个Follower上线,向它们发送日志。然后在对剩下的仅有3个节点的Raft集群重复上面网络分区的过程。
- TestCount2B:检查无效的RPC个数,不能过多。
第一轮调试
卡在了TestFailAgree2B
打点之后能看到,追加entry{106, 1}之后,换了新的leader,而这个新的leader0之前处于死亡状态,现在恢复连接之后竟然成为了leader。
查看test的源码,确实是在发送entry{107,1}的时候换了leader
发现问题之后就是解决问题,现在要做的就是要同步leader和整个状态机之间的日志
还剩一堆错
raft简化实现的一部分:增量的过程只会从leader到followers单向进行。