Raft 面试必备
Raft
Raft协议呢,一般包括这三种节点:Follower、Candidate、Leader;其中,候选人是只有在选举期间才存在的节点,一旦选举结束,那么集群中就只有 Leader,Follower
节点之间使用 rpc 来通信,包括 投票rpc,复制日志心跳rpc,快照rpc
选举过程如下:
选举时机:当网络刚刚启动或者集群节点变更,或者上一任期结束后。所有节点进入选举阶段,每个人持有一个随机的选举时钟,时钟结束后,节点优先给自己投票,然后广播投票rpc。会有三种结果:赢得多数选票成为 Leader; 其他人当选,自己成为 follower;没有人当选,开启下一轮投票。
由于raft的机制,可以认为能够成为 Leader 的节点,一定是包含所有日志的节点
leader选举的过程是:1、增加term号;2、给自己投票;3、重置选举超时计时器;4、发送请求投票的RPC给其它节点
Leader 被选举出来以后,就开始接收客户端的消息。Leader 会将这一条消息作为日志记录下来,并将其通过心跳包同步给集群中的 follower,当大部分节点都同步这条日志以后,Leader 将这个请求应用到自身的状态机并回复客户端。follower 如果发生宕机或者丢包,Leader 会不断尝试直到所有节点都同步了这条日志。
日志由有序编号(log index)的日志条目组成。每个日志条目包含它被创建时的任期号(term)和用于状态机执行的命令。
日志复制的过程有两条保证:
- 如果不同节点的两条日志有相同的索引和任期,那么他们存储的命令是一致的(由于主从同步的特性,Leader 节点上的日志和命令落库后就不会更改)
- 如果不同节点的两条日志有相同的索引和任期,那么他们之前存储的日志也是一致的(由于日志的一致性检查,当 follower 收到来自 Leader 的心跳包以后,会与本地的日志索引和任期做匹配,如果有不一致,那么会拒绝掉这个心跳包)
日志复制的异常情况包括:
Leader 宕机或者崩溃,旧 Leader 没有复制完所有的日志。可能多,可能少,可能不一致
日志复制的过程,需要通过与 Leader 保证强一致性来保证安全复制。当日志产生不一致时,Leader 会从后往前尝试同步日志心跳包给其他节点,直到找到一个双方都符合的日志,然后 Leader 会把从这条日志以后的所有内容同步给该节点,将不一致的部分覆盖掉,直到与 Leader 保持完全一致
当 leader 和 follower 日志冲突的时候,leader 将校验 follower 最后一条日志是否和 leader 匹配,如果不匹配,将递减查询,直到匹配,匹配后,删除冲突的日志
那么像之前提到 Leader 中的日志可能也不是最新的,会不会同步出去的日志本身就是异常的呢?
这个就是 raft 通过两条限制来保证了日志复制的安全性:
- 只有log索引和任期是最新的节点才有可能成为 Leader,这个在选举投票期间是可以判断的,非最新的就不给他投票
- Leader 只能推进日志的索引进行提交,以前任期的要在检查一致性的过程中同步过去。
那么在实际使用过程中,为了保证日志不会无限增长,每隔一段时间,每个节点独立的进行快照,将某个时间点以前的日志落库后丢弃。
当 Leader 在同步日志的过程中发现某个节点的日志特别老,那么这时候,Leader 会发送快照rpc来将日志打包复制。
当然实际使用中,需要控制间隔的时间,和快照的频率(防止发生 io 阻塞),一般达到固定大小发生一次。
同时可以使用 copy-on-write 技术来保证正常的日志同步
脑裂问题是说一次性在集群中增加了太多的节点,比如超过了原来节点数量的一半,那么就有可能产生双主的现象,即一个集群中有两个 Leader,彼此成员之间没有交集。解决办法是一次变更一个节点,少量多次的来完成。
另外还有一个情况是,如果发生了网络分区故障或者异常,导致老 Leader 失联了,剩下的节点会重新选举出一个新 Leader,并与客户端继续交流。当老 Leader 恢复连接以后,他上面的 commit 都会被视作失效,本身会转化为 follower 接收从新 Leader 来的日志。
另外一种还有一个 Prevote 机制
当有一个 follower 与集群隔离后,他会自己进入候选阶段,并由于无法获得投票成为 Leader 而一直刷新任期,导致该节点任期非常大。当该节点重进集群以后,会由于任期导致选举混乱,因此 raft 采用 prevote 机制,是一个类似于两阶段提交的协议,第一阶段先征求其他节点是否同意选举,如果同意选举则发起真正的选举操作,否则降为Follower角色。这样就避免了网络分区节点重新加入集群,触发不必要的选举操作