Raft 是一种为了管理复制日志的一致性算法,该算法强依赖 Leader 节点的可用性来确保集群数据的一致性,即如果client向leader发起添加日志请求,如果leader回复client提交成功,那么该日志一定被过半数的server所提交,且是确定的不会被其他日志覆盖。
算法包含2类成员,server和client:
- server:接受client的写日志请求,该类成员包含3种角色:
- follower:跟随者,server集群首次启动时都是follower。
- candidate:候选人,在follower的选举时间超时后会变为candidate。
- leader:领导者,当server成为candidate后并发起选举投票请求收到过半数server的投票后会变为leader。
- client:负责发起附加日志请求到server
算法包含两个过程:leader选举和日志复制:
1.leader选举
- 该过程发生在server集群启动或leader宕机时,server最初都是follower,每个follower都维持一个选举超时时间,当选举超时时间到达后,follower会自动晋升为candidate,并发起投票请求给其他server,如果收到过半数server的投票同意响应,则晋升为leader,然后定时发送心跳请求给其他server(周期远小于选举超时时间),如果有了新的leader,那么其他server都变为follower,当收到leader的心跳请求后会重置自己的选举超时时间。
- 每次选举都会产生一个新的递增的任期号。
- 在server收到投票请求时,只有对方的最新日志比自己的日志新才会回复同意投票响应给对方,日志用任期号和日志索引来比较日志新旧,先比较任期号,再比较索引,任期号是日志从client发送到leader时该leader的任期号。
- 在一次选举过程中,每个server最多只能投一次票。
2.日志复制
当选出一个leader后,便能够为client提供服务啦,即处理client的写日志请求,所有client的写请求最终都会发送到leader,leader收到client的写日志请求后,会执行如下:
- 将日志写到本地文件;
- 将日志复制给其他follower,其他follower收到日志后写到本地文件并响应leader写入成功;
- 当leader收到follower的响应并且已经把日志复制到过半数的server节点(包括自己)后,commit该日志(把日志刷新到最终要保存的磁盘位置),并通知其他follower commit该日志。注意:只有该日志的任期号跟自己的任期号相同才能commit,上个任期号的日志在当前任期leader不能被直接commit,需要当前任期leader在commit当前任期日志时该日志索引之前的日志才会被自动commit。
注意:当server节点宕机后节点数量不会发生变化,即该宕机的server还算做集群的一员,在选举和复制日志过程中还必须考虑其投票和复制日志响应。
日志处于两种状态才是安全的,如下:
- 第一种状态是被commit的日志,该日志已经占据了日志索引位置,在以后才绝对不会被丢失和覆盖;
- 第二种状态是该日志被复制到过半数的server,且该日志任期号与当前leader任期号相同;
第一种状态无需解释,针对第二种状态,即使该日志没有被commit,然后leader宕机了,那么没有该日志的server节点不可能选举成为leader,只有包含该日志的server才能被选举为leader,在选举成为leader后在复制日志给其他follower后并且也把自己任期的一条日志复制给过半数server后会commit自己任期的该条日志,那么上一个任期的日志也会被间接commit,注意,raft算法不会单纯的commit上一个任期的日志,只能在commit自己任期的日志时间接commit上一个任期日志。下面简单证明为什么leader把当前任期的日志复制到过半数的follower后相当于日志就是安全的了:
假设有5台server,包含1台leader4台follower,当有2台follower收到日志后该日志是安全的并且可以commit的,假设leader编号为1,任期号为1,收到日志的2台follower编号为2、3,没收到日志的3台follower编号为4、5,如下(表格第一行的1、2表示日志索引位置,日志内容<t,c>,t表示任期号,c表示日志内容):
1 | 2 | |
---|---|---|
server 1(master) | <1,x->1> | |
server 2 | <1,x->1> | |
server 3 | <1,x->1> | |
server 4 | ||
server 5 |
如果这时server 1宕机了,但集群数量还是5(server节点宕机后也算做集群一员),那么只有比5/2+1=3台server(包括自己)的日志新才能选举为leader,所以只有follower 2、3才能成为leader,假设2成为了leader,任期号为2,它会将日志<1,x->1>复制给其他follower,如下:
1 | 2 | |
---|---|---|
server 1 | <1,x->1> | |
server 2(master) | <1,x->1> | |
server 3 | <1,x->1> | |
server 4 | <1,x->1> | |
server 5 | <1,x->1> |
然后server 2收到client的写日志请求,收到日志y->1,会把该日志复制给其他follower:
1 | 2 | |
---|---|---|
server 1 | <1,x->1> | <2,y->1> |
server 2(master) | <1,x->1> | <2,y->1> |
server 3 | <1,x->1> | <2,y->1> |
server 4 | <1,x->1> | |
server 5 | <1,x->1> |
如果server 2收到了过半数server的写日志成功响应,假设收到了server 1、3的响应,那么可以commit日志<2,y->1>了,如果通知其他follower commit成功那么之前的日志<1,x->1>也会被间接commit。
也就是说,过半数5/2+1=3,这个数量一定比未收到复制日志<1,x->1>的follower的总和2要大,所以不管有几台server宕机(当然不能超过5/2=2台节点宕机),未收到复制日志<1,x->1>的节点(server 4、5)都不会收到过半数server的投票而选举为leader(因为它一定没有比过半数server的日志新),只有包含那条复制到过半数server的日志<1,x->1>的server节点(server 1、2、3)才能选举成为leader,当其选举为leader后会把该日志<1,x->1>复制给其他follower,最终还是能成功commit该日志。由此可见,只有过半数5/2+1=3的server节点存活才能保证服务可用,如果server 1、2、3全宕机了那服务本身就不可用了,server 4、5不能提供服务不能接收日志写服务,也就不存在日志<1,x->1>被覆盖问题,在server 1、2、3重启之前永远不能提供服务。
下面解释下为什么leader在把上一个任期的日志复制到过半数server后该日志还是不安全的:
假设有5台server,1台leader,4台follower,leader编号为1,任期号位1,假设leader把上一个任期的日志复制给了follower 2,然后宕机了,如下(表格第一行的1、2表示日志索引位置,日志内容<t,c>,t表示任期号,c表示日志内容):
1 | 2 | |
---|---|---|
server 1(master) | <1,x->1> | |
server 2 | <1,x->1> | |
server 3 | ||
server 4 | ||
server 5 |
然后server 5通过server 3、4选举成为leader,任期号为2,并把一条新日志复制给了server 4,然后又宕机了如下:
1 | 2 | |
---|---|---|
server 1 | <1,x->1> | |
server 2 | <1,x->1> | |
server 3 | ||
server 4 | <2,y->1> | |
server 5(master) | <2,y->1> |
然后server1通过server2、3选举成为leader,任期号为3,把日志<1,x->1>复制给了server 3,然后又宕机了,如下:
1 | 2 | |
---|---|---|
server 1(master) | <1,x->1> | |
server 2 | <1,x->1> | |
server 3 | <1,x->1> | |
server 4 | <2,y->1> | |
server 5 | <2,y->1> |
然后server 5又通过server 3、4选举成为leader,任期号为4,它会把日志<2,y->1>复制给其他follower,复制给server 1、2、3时会覆盖在索引位置为2的<1,x->1>日志,因为该日志没有被commit所以可以被覆盖,如下:
1 | 2 | |
---|---|---|
server 1 | <2,y->1> | |
server 2 | <2,y->1> | |
server 3 | <2,y->1> | |
server 4 | <2,y->1> | |
server 5(master) | <2,y->1> |
这种情况下虽然server 1在成为leader且任期号为3,把日志<1,x->1>复制到了过半数server,即server 1、2、3,该日志最后还是被server 5的<2,y->1>覆盖了。