可能是你能找到的最完整最详细的中文版的Raft算法说明博客
内容来源都是原生的论文,可以保证内容的可靠性,并且对论文里面的很多细节做了扩展说明
章节介绍
Raft系列1 基本概念(原创)
https://www.jianshu.com/p/0e695b4be92f
Raft系列2 核心概念(角色&Rules)
https://www.jianshu.com/p/00e89b5e9669
Raft系列3 总体心得
https://www.jianshu.com/p/496981a62bbe
Raft系列4 任期和选举
https://www.jianshu.com/p/aa137c94ac0b
Raft系列5 客户端设计
https://www.jianshu.com/p/bb7388b0cde6
Raft系列6 AppendEntries RPC
https://www.jianshu.com/p/d3247e2203a6
Raft系列7 快照技术和动态修改配置
https://www.jianshu.com/p/12e8bc4f384c
Raft系列8 一致性分析
https://www.jianshu.com/p/6344f202b7e5
Raft集群的模型介绍
图形来看有2个内容:request的完整流程走向、一个Node的主要构成
- 数据流程的走向:
- 客户端发起一个命令,比如设置 x->3,过程1
- 会通过Raft集群的一致性模型的管理,最终request会传达到leader节点
- leader会把这个操作步骤写在自己本地日志,并且会通过一致性模型,通知到其它的各个follower节点,过程2
- 超过半数follower复制反馈成功,leader再把这个操作应用到状态机中,可能以前状态机中没有x的值,或者x的值为2,最终都会在状态机中生成,x->3,过程3
- 最后再返回给客户端,操作成功,过程4
- 一个Node的主要构成: 一致性模型、日志、状态机
log[](command)
log除了有index的概念(下标从1开始),还有term的概念(这个log是在哪个term添加的),另外log本身就是一个命令 command,比如 x->3
raft集群中的log指的是一个命令,或者说是一个操作而不是一个value,比如 x 设置为 3,或者 y 设置为 5.
log[]中有的命令数组,不一定是最终会执行的命令。只有半数通过,最终leader commit了,才会apply到状态机中。
一个log[]完成不了一致性的功能,还依赖Node的commitIndex(半数通过了的命令),lastApplied(最终运用到状态机当中的命令)下标,最终实现一致性
关于log的操作有3种:copy、commit、apply。 copy是leader把新日志发送给所有follower的过程,commit是leader统计到超过半数的节点完成了复制,然后更新自己的commitIndex的过程,apply是完成更新commitIndex时,节点把最新commit的命令,按照顺序执行到自己的状态机里面的一个过程
状态机state Machine
- 每个节点都有一个状态机,一个Raft集群就是一组状态机,zookeeper也是通过这种方式实现一致性管理的(复制状态机)
- 注意状态机和日志的区别,日志存储的是数据操作的操作日志,状态机存储的是数据操作的结果值。
比如客户端发起3个操作,x:1, x:2, x:3,则有3个操作日志,但是存储在状态机中只有1个键值对: x:3。
状态机的作用:
- 存储的数据是这个值的最终态,而不是过程态(比如 x:3),可以提升查询的效率,直接返回结果
- 存储半数节点都通过的数据(x:3是半数节点一致通过的结果,而log里面的命令,不一定会被执行,可能因为leader的失败,而导致这个命令删除或者被覆盖掉)
日志的作用:管理和同步数据用的。日志里面的数据(command)可能会被覆盖。
Server的详细组成
每个状态机因为role不同,会存储不同的内容
会持久化的内容: (每次响应RPC的时候都会update值到硬盘上)
- currentTerm
- votedFor
- log[]: log有index,term,command等概念
不会持久化的内容:
每个节点都有一个状态机,来存储所有command按照日志顺序结构执行的最终结果
所有临时存储的内容:commitIndex、lastApplied(节点收到leader的最新的commitIndex,更新完自己的commitIndex之后,开始把最新的log里面的command apply到状态机中,操作结束后更新lastApplied的值),所以理论上commitIndex的值和lastApplied的值大部分的时候是相等的,或者讲当server发现lastApplied<commitIndex,会执行apply的操作
Leader节点存储的临时的内容(每次重新选举会初始化):
- nextIndex[] :下一个待发送日志的下标,初始化值是 新leader的last Log Index +1
- matchIndex[]:日志最匹配的一个下标,初始化值是0
非常巧妙的一个设计,理论上正常情况下,matchIndex=nextIndex-1
比如leader的日志条目数是10个且最新没有要添加的日志,其中4个follower,数据都是同步的,那么nextIndex[] : 11,11,11,11 。matchIndex[] 为 10,10,10,10
如果当前leader有一个新的日志要添加,那么AppendEntries RPC到各个follower,response成功后,更新nextIndex[] 和matchIndex[]
主要的发挥用途是在状态异常的时候
- 老leader挂了,新leader当选,默认nextIndex为11,matchIndex为0,发送一次同步之后,如果数据都一样,那么更新为 nextIndex为11,matchIndex为10。
如果数据有gap,AppendEntries RPC response false,那么nextIndex会递减1,leader发送的log为 log[10],如果还失败 继续递减,直到返回true,再挨个的同步数据
- 一个计时器(计算心跳超时和 选举超时)
- 一个统计器(leader会统计成功复制的节点个数是否超过半数,candidate会统计获得的投票数是否超过半数)
注意事项:
- leader为了提升性能,会并发性的给所有follower发消息,但是每个RPC的内容可能是不同的,是根据nextIndex[]来发的,如果所有的follower的状态都一致,那么nextIndex[]的值都一样
- 感觉matchIndex[]和nextIndex[]有冗余,因为matchIndex[]的index+1就是nextIndex的值
RPC
- Raft中的RPC分为2种大的类型:选举消息和状态同步类消息。按照数据结构来分为3种RPC:RequestVote RPC、AppendEntries RPC、InstallSnapshot RPC。按照功能分为5种RPC(选举、同步log、心跳、新leader的宣誓主权的消息、同步快照)。
- 日志同步类消息分为:AppendEntries RPC和 InstallSnapshot RPC(快照方式同步数据)
- AppendEntries RPC按照功能来分:heartbeat功能的消息、同步log的消息、宣誓主权的同步内容为空的log消息(可以确保老leader遗留给新leader的消息仍然会被执行)
- Raft集群间的RPC消息是双向的,就是消息发起人发起一个消息,消息接收人会返回一个结果,这才构成一个完整的消息
- 如果 follower 崩溃或者运行缓慢,或者网络丢包,leader 会不断地重试 AppendEntries RPC(即使已经回复了客户端)直到所有的 follower 最终都存储了所有的日志条目
- Raft 的 RPCs不仅交互失败会发起重试,而且这种重试都是幂等的,所以这样的重试不会造成任何伤害。例如,一个 follower 如果收到 AppendEntries 请求但是它的日志中已经包含了这些日志条目,它就会直接忽略这个新的请求中的这些日志条目。
参考
主要参考Stanford University的原生论文以及中文的翻译版本
Raft算法中文版(论文翻译)
https://www.cnblogs.com/linbingdong/p/6442673.html