raft
协议
Nacos 默认支持 AP,但同时又支持 CP ,底层使用 raft(jraft)实现 leader 的选举,所以我们需要学学 raft
什么是raft
?
简单说Raft
是一套共识算法,是 paxos
的变种,解决分布式系统中多个副本的一致性问题,通过多副本来进行容错,以此在提高系统可用性。
说到底raft
的诞生就是为了更简单的解决分布式系统中每个系统的一致性问题
那么raft怎么解决一致性问题呢?
raft将一致性问题分为
- leader选举
- 日志复制
- 安全措施
leader 选举:就是在整个分布式系统中选出一个 主节点 对外提供服务
日志复制:就是让从节点和主节点的状态保持一致
安全措施:就是在选举的过程中(或者整个系统存在一些问题时),保证系统依旧可用
接下来,我们需要对上面三个部分进行分析
1. leader选举
raft
协议在进行leader
选举的过程中,会将节点置于三个状态:
follow
从节点,给其他节点提供选票candidate
候选者,由follow
变化而来,如果一个follow
在一定时间内没有收到leader
的心跳包时,就会将自己的状态修改为candidate
,并开启新一届的任期term,启动新一轮的选举流程leader
主节点,如果candidate
选举成功,则转化为leader
,并开始对外提供服务
在分布式系统刚开始前所有节点都是 follow
从节点
然后follow
发现没有收到 leader
的心跳包,将自己的状态修改为 candidate
候选者状态
接着候选者向其他从节点发送选票
从节点将会回复选票给候选者
右下角的节点就变成 Leader
了
需要注意,
Term = 0
任期包含选举的过程哦!
所有对系统的修改都将跟 Leader
挂钩
leader
选举过程结束
节点间的数据复制
发送给 leader
节点的数据将会被记录在节点的日志中,此时由于数据没有同步到其他节点,所以 leader
节点的值没有变成 5
leader
向其他从节点发送数据:
从节点将值记录到自己的日志中,然后发送ack
给 leader
节点, 告诉他我已经把值保存到日志中了
此时 leader
节点的值将会被修改为 5
,并且标记日志中的这条记录为已提交
接着 leader
将发送消息给其他从节点,表示我的值已经修改了,你们也可以将日志的值提交
此时整个系统达到共识,日志复制过程结束
在 raft
中有两个超时设置控制着leader
选举
第一个是 选举超时,选举超时是 follow
变成 candidate
的计算时间,只要超时,则该从节点就会变成 candidate
而选举超时时间不可能被设置成相同的时间,这样会导致多个节点的选举超时时间都被耗尽,全变成candidate
候选者
一般超时时间介于 150ms ~ 300ms
之间随机选取
当一个 follow
节点变成了 candidate
时,term
将会被自增1
,接着为自己投一票,并且发送投票消息给其他节点
如果接受投票消息的节点在这个 term
(此时从节点的term
也自增为 1
)中还没有投票,那么它会投给 candidate
候选者
从节点发送ack
完毕之后,将自己的 选举超时 时间重置,重新开始倒计时
candidate
收到从节点的 ack
之后,立即变成 leader
leader
第一时间发送 append entries
消息给其他从节点
这些消息将以心跳超时指定的间隔时间发送
接着从节点响应 append entries
消息回 leader
这中间将数据进行了一次同步,演示里面没说明
最后选举的过程结束,直到从节点 选举超时 时间超时,从节点变成新一任的 candidate
现在新的问题发生了, leader 节点从集群中消失了,此时从节点选举时间超时,变成 candidate ,term++,接着发出 vote 投票消息给其他从节点,此时集群中只有两个节点
一个是 candidate
另一个是 follow
follow ack
消息给 candidate
, term++
并重置 选举超时时间,
candidate
接收到 ack
变成 leader
之后发送 append entries
message 给 follow
节点
这中间 新 leader
还是会发送 发送心跳包 给 Node B
(已经挂掉的节点)
分裂投票情况(脑裂问题)
两个follow
节点在某个时间同时变成 candidate
导致集群出现脑裂问题,raft
怎么解决这个问题?
此时每个 candidate
获得的选票都是 2
, 相等了,且没有更多有票的 follow
raft
给出的方案就是选票数量重置为 0 ,再选一次
因为每个 选举超时
的时间大概率不相等,所以此时选举大概率会选举出一个 leader
节点状态变化图:
2. 日志复制
一旦选出了一个leader
之后,我们需要将系统所有更改的数据复制给所有节点
通过使用相同心跳包的 append entries
消息完成日志复制
来看看完整过程:
首先通过客户端发送一个数据给 leader, leader
收到数据保存到日志
这个数据在下次心跳包时会发送给 follows
一样的步骤,follows
返回ack
给 leader
, leader
立即将日志的数据提交到leader
中
此时 leader
响应一个消息给 client
(也就是图中的绿色部分)
leader
发送已经保存的消息给其他follows
,告诉他们可以将日志的数据提交了
现在客户端再次发送一个消息,add 2
将 leader 的数据从5 增加到 7,这里就不详解了,具体步骤相同
甚至 raft
可以在面对网络分区时保持一致
现在添加一个网络分区,去分离所有节点
分离之后上面的三个节点需要重新选择leader
此时上面三个节点的 term
任期会被下面的任期大一级,因为上面进行了选举
下面的 client
发送 set 3
给 Node B leader
, Node B
发现无法发送给绝大多数的节点
Node B
还记录着ACDE
节点的信息
发现只有 Node A
做出了反应,绝大数节点还没反应,所以无法将值写入到节点中
但不影响 Node C leader 修改其 follows 的能力
图中client
发送了 set 8
给 Node C leader
, Node C
只记录了 Node D
和 Node E
所以可以将日志中的值提交,并拷贝给 Node D
和 Node E
现在我们修复网络分区问题
此时可以看到 Node C leader
的 term
高于下面的 Node B leader
,所以 Node B leader
的Leader
状态被撤销
Node B
和 Node A
都将回滚未提交的消息,并配合 Node C leader
的日志
也就是说原先的
set 3
消息没了?对,没了。。。客户端不会收到
set
成功的消息了
如果想深入研究 raft 可以去看他的 paper: In Search of an Understandable Consensus Algorithm (raft.github.io)
至此 raft 入门结束