这个博客仅供个人面试学习使用,既要清晰,又不能啰里八嗦一大堆。
1. 资源
- 很友好的动画:thesecretlivesofdata.com/raft/
- Raft官网:raft.github.io/
- Raft论文:raft.github.io/raft.pdf
2. 过程描述
我觉得介绍Raft需要从3个部分:领导者选举、日志同步、安全性。
领导者选举:
- 刚开始所有节点都是Follower,等待一个超时时间后,没有收到Leader的心跳包,则将term+1,自己成为候选者,向其他所有节点发送投票请求,并给自己投一票。
- 从节点投票时,要求主节点的日志“至少不比自己落后”(先比较term,term相同比较index,index即最后一条日志的索引号)。这个机制叫:Log Up-to-date Rule。
- 候选者如果收到超过半数的投票,则成为主节点,并且每隔一段时间向从节点发送心跳包。从节点收到心跳包,会重置自己的计时器,并且如果心跳包中的term比当前从节点的term大,那么从节点会更新自己的term。
- 如果选举不成功,比如4个节点,两个候选人一人两票,那么等计时器超时,会有节点将term+1,发起下一轮投票。
日志同步:
- 主节点收到客户端的请求,把数据封装成 logentry追加到日志中。
- 主节点并行地向所有从节点发送日志复制消息。
- 从节点收到后,将logentry追加到日志,并给主节点返回ACK。
- 主节点收到超过半数的ACK之后,将logentry应用到状态机,代表日志真正被提交,向客户端返回成功,并且通知所有的从节点提交日志。
- 从节点收到提交日志的消息后,将logentry应用到状态,日志被提交。
安全性:
Raft的安全性保障3个方面:
- 选出的Leader的日志一定是最新的。换言之,已提交的日志不会因为换Leader就丢失。
- 日志一旦提交,最终一定会同步到所有存活的节点。(提交的日志不会回滚)
- 日志前缀一致性,避免“同一位置有不同内容”的分歧,即 term相同 && index相同,则日志一定相同。
如何保证这三个事情的?
- 选举安全性:节点只会给“至少跟自己一样新”的候选人投票。这个机制叫:Log Up-to-date Rule。
- 日志匹配安全性:Leader 在 AppendEntries RPC(追加日志请求) 里会带上前一个日志的 term 和 index,Follower 必须匹配才会接受新的日志,否则会回滚到匹配点。
- 多数派一定是最新的,多数派才能成为主节点,主节点一定是最新的。
3. 网络分区
假设有5个节点,发生了网络分区,无非是1对4,或者2对3,总归有多数派的一边。那么多数派能正常选出主节点,但是少数派那边会因为始终收不到多数节点的投票,而导致任期迅速增长。等到网络分区解决,少数派的term大,但是数据落后。此时会发生什么?
假设少数派为:D、E,term=100,数据落后。多数派为:A、B、C,term=50,数据新。
- 网络分区恢复,D给ABC发送投票请求,携带自己的term=100.
- ABC一看,收到的term比自己的大,将自己的term也更新为100,但是拒绝投票,因为D的数据落后。
- D没能当选。
- 假设A超时了,进入下一轮选举,此时A的term++,变成101,数据又是最新的,能当选。
还有就是,部署的时候,最好不要搞偶数个节点,因为这样的话,可能出现网络分区后,哪一边也选不出主节点的情况,这会导致集群处于无法写入的状态。(也可见Raft是牺牲A来换取C)