引言
一个有意思的问题:
甲乙两人轮流在一张圆桌上平放黑白围棋子,每次放一子,棋子不许重叠,
谁先没有地方放就输。请问怎样放才能赢?
答案: 先行者必胜
三种不同的思维方式:
- 假如桌子只有一个围棋子那么大
- 假如桌子无限大,先行者先占住圆心,由于圆是对称图形,所以只要对手还能找到位置放,你总能在对称的另一面找到位置放
- 一个圆中可画单数个直径相等且互切的小圆(即最后一粒棋子一定是单数)
三种不同的思维方式在可理解性难度上逐渐加深。第一种是极简化思维,但数学上是不严谨的。第二种是极限思维,和第一种结合起来就是数学归纳法了,在数学上是严谨的。第三种是形象思维,使用了几何学概念,但对于没有几何学基础知识的人就很难理解了
最近研究了下Kafka,MySQL,Elasticsearch分布式数据一致性的解决方案,所以非常有必要来看下Raft算法
一、Raft算法介绍
Raft是工程上使用较为广泛的强一致性、去中心化、高可用的分布式协议,注意这里强调是工程上,因为在学术理论界,最耀眼的还是大名鼎鼎的Paxos,这可是获得图灵奖的算法,但是对于普通人来说太难理解了。
raft是一个共识算法(consensus algorithm),所谓共识,就是多个节点对某个事情达成一致的看法,即是在部分节点故障、网络延时、网络分割的情况下。分布式系统中,共识算法更多用于提高系统的容错性,比如分布式存储中的复制集(replication)
二、选主流程
![](https://github.com/AlgoricLi/MyImage/blob/master/algorithms/Raft%E7%AE%97%E6%B3%95%E8%A7%92%E8%89%B2.png?raw=true)
Raft算法定义了三种角色:
- Leader(领导者)
- Follower(追随者)
- Candidate(候选者)
初始状态下集群所有节点都是Follower,每一个节点都有自己的计时器,当计时达到了超时时间(Election Timeout),该节点会转变为Candidate,成为Candidate的节点,会首先给自己投票,然后向集群中其他所有的节点发起请求,要求大家都给自己投票,其他收到投票请求且还未投票的Follower节点会向发起者投票,发起者收到反馈通知后,票数增加。
得票数超过了集群节点数量的一半,该节点晋升为Leader节点。Leader节点会立刻向其他节点发出通知,告诉大家自己是领导者。收到通知的节点全部变为Follower,并且各自的计时器清零。如果发现有节点票数相同,那么会随机休息一段时间,再次发起投票,由于时间是随机,所以避免再次票数相同的概率
选出 Leader 后,Leader 通过定期向所有 Follower 发送心跳信息维持其统治。若 Follower 一段时间未收到 Leader 的心跳则认为 Leader 可能已经挂了再次发起选主过程
三、数据写入流程
首先明确一点,数据的流向只能从 Leader 节点向 Follower 节点转移
- 由客户端提交数据到Leader节点
- Leader 节点接收到的数据处于未提交状态(Uncommitted)
- 由Leader节点把数据复制到集群内所有的Follower节点
- Follower节点们接收到复制的数据,会反馈给Leader节点
- 如果Leader节点接收到超过半数的Follower反馈,表明复制成功,向 Client 确认数据已接收
- 一旦向 Client 发出数据接收 Ack 响应后,表明此时数据状态进入已提交(Committed)
- 再由Leader节点通知集群内所有的Follower节点提交数据,从而完成数据同步流程
四、数据同步流程
在上述过程中,主节点可能在任意阶段挂掉,看下 Raft 协议如何针对不同阶段保障数据一致性的
1. 数据到达 Leader 节点前
Leader 挂掉不影响一致性
2. 数据到达 Leader 节点,但未复制到 Follower 节点
这个阶段 Leader 挂掉,数据属于未提交状态,Client 不会收到 Ack 会认为超时失败可安全发起重试。Follower 节点上没有该数据,重新选主后 Client 重试重新提交可成功。原来的 Leader 节点恢复后作为 Follower 加入集群重新从当前任期的新 Leader 处同步数据,强制保持和 Leader 数据一致
3.数据到达 Leader 节点,成功复制到 Follower 所有节点,但还未向 Leader 响应接收
这个阶段 Leader 挂掉,虽然数据在 Follower 节点处于未提交状态(Uncommitted)但保持一致,重新选出 Leader 后可完成数据提交,此时 Client 由于不知到底提交成功没有,可重试提交。针对这种情况 Raft 要求 RPC 请求实现幂等性,也就是要实现内部去重机制
4. 数据到达 Leader 节点,成功复制到 Follower 部分节点,但还未向 Leader 响应接收
这个阶段 Leader 挂掉,数据在 Follower 节点处于未提交状态(Uncommitted)且不一致,Raft 协议要求投票只能投给拥有最新数据的节点。所以拥有最新数据的节点会被选为 Leader 再强制同步数据到 Follower,数据不会丢失并最终一致
5. 数据到达 Leader 节点,成功复制到 Follower 所有或多数节点,数据在 Leader 处于已提交状态,但在 Follower 处于未提交状态
这个阶段 Leader 挂掉,重新选出新 Leader 后的处理流程和阶段 3 一样
6. 数据到达 Leader 节点,成功复制到 Follower 所有或多数节点,数据在所有节点都处于已提交状态,但还未响应 Client,即响应Ack延迟
这个阶段 Leader 挂掉,Cluster 内部数据其实已经是一致的,Client 重复重试基于幂等策略(要保证幂等)对一致性无影响
7.脑裂
由于网络延迟,Leader与其它Follower之间心跳超时,在Follower选出了新的Leader,向原Leader节点提交的数据不会再节点间进行复制,网络恢复后由于Leader节点存在各自的epoch(统治时期),原Leader会自动降级成Follower,并从新 Leader 处同步数据完成数据的最终一致性
参考
https://www.cnblogs.com/mindwind/p/5231986.html