Contents
  1. 1. 概念与术语
  2. 2. Leader选举
    1. 2.1. Leader流程
    2. 2.2. Candidate流程
    3. 2.3. Follower流程
  3. 3. 日志复制流程
    1. 3.1. leader流程
    2. 3.2. follower处理流程
  4. 4. 快照流程
    1. 4.1. leader发送快照流程
    2. 4.2. follower接收快照流程
  5. 5. 集群配置变更
    1. 5.1. 变更流程

概念与术语

Leader:领导者,为客户端提供服务(生成写日志)的节点,任何时候Raft系统中只能有一个Leader。
Follower:跟随者,被动接受请求的节点,不会发送任何请求,只会响应来自Leader或者Candidate的请求。如果接受到客户请求,会转发给Leader。
Candidate:候选人,选举过程中产生,Follower在超时时间内没有收到Leader的心跳或者日志,则切换到Candidate状态,进入选举流程。
TermId:任期号,时间被划分成一个个任期,每次选举后都会产生一个新的TermId,一个任期内只有一个Leader。TermId相当于Paxos的ProposalId。
currentTerm:更新的Term。
RequestVote:请求投票,Candidate在选举过程中发起,收到Quorum(多数派)响应后,成为Leader。
AppendEntries:附加日志,Leader发送日志和心跳的机制
Election Timeout:选举超时,如果Follower在一段时间内(超时时间为150ms-300ms内随机数)没有收到任何消息(追加日志或者心跳),就是选举超时。
Leader Election:领导选举,选举超时即开始领导选举。
Raft协议主要包括三部分:

  • Leader选举
  • 日志复制
  • 成员变更

一个节点的状态只有如下三种形式的一种:

  • Follower 状态
  • Candidate 状态
  • Leader 状态

Leader选举

Leader流程

服务器启动时初始状态都是Follower,如果在超时时间内没有收到Leader发送的心跳包,则进入Candidate状态进行选举,服务器启动时和Leader挂掉时处理一样。为了避免选票瓜分的情况,比如5个节点ABCDE,Leader A 挂掉后,还剩4个节点,raft协议约定,每个服务器在一个任期只能投一张票,假设B,D分别有最新的日志,且同时发起选举投票,则可能出现B和D分别得到2张票的情况,如果这样都得不到大多数确认,无法选出leader。为了避免这种情况发生,raft利用随机超时机制避免选票瓜分情况。选举超时时间从一个固定的区间随机选择,由于每个服务器的超时时间不同,则leader挂掉后,超时时间最短且拥有最多日志的follower最先开始选主,并成为leader。一旦candidate成为leader,就会向其他服务器发送心跳包阻止新一轮的选举开始。
发送日志信息:(term,candidateId,lastLogTerm,lastLogIndex)

Candidate流程

1.在超时时间内没有收到leader的日志(包括心跳)
2.将状态切换为candidate,自增currentTerm,设置超时时间
3.向所有节点广播选举请求,等待响应,可能会有以下三种情况:
(1).如果收到多数派回应,则成为leader
(2).如果收到leader的心跳,且leader的term>=currentTerm,则自己切换为follower状态,
否则,保持Candidate身份
(3).如果在超时时间内没有达成多数派,也没有收到leader心跳,则很可能选票被瓜分,则会自增currentTerm,进行新一轮的选举

Follower流程

1.如果term < currentTerm,说明有更新的term,返回给candidate。
2.如果还没有投票,或者candidateId的日志(lastLogTerm,lastLogIndex)和本地日志一样或更新,则投票给它。
注意:一个term周期内,每个节点最多只能投一张票,按照先来先到原则

关键词:随机超时,FIFO

日志复制流程

leader向follower发送日志时,会顺带邻近的前一条日志,follwer接收日志时,会在相同任期号和索引位置找前一条日志,如果存在且匹配,则接收日志;否则拒绝,leader会减少日志索引位置并进行重试,直到某个位置与follower达成一致。然后follower删除索引后的所有日志,并追加leader发送的日志,一旦日志追加成功,则follower和leader的所有日志就保持一致。只有在多数派的follower都响应接受到日志后,表示事务可以提交,才能返回客户端提交成功。
发送日志信息:(term,leaderId,prevLogIndex,prevLogTerm,leaderCommitIndex)

leader流程

1.接收到client请求,本地持久化日志
2.将日志发往各个节点
3.如果达成多数派,再commit,返回给client。
备注:
(1).如果传递给follower的lastLogIndex>=nextIndex,则从nextIndex继续传递
.如果返回成功,则更新follower对应的nextIndex和matchIndex
.如果失败,则表示follower还差更多的日志,则递减nextIndex,重试
(2).如果存在N>commitIndex,且多数派matchIndex[i]>=N, 且log[N].term == currentTerm,
设置commitIndex=N。

follower处理流程

1.比较term号和自身的currentTerm,如果term<currentTerm,则返回false
2.如果(prevLogIndex,prevLogTerm)不存在,说明还差日志,返回false
3.如果(prevLogIndex,prevLogTerm)与已有的日志冲突,则以leader为准,删除自身的日志
4.将leader传过来的日志追加到末尾
5.如果leaderCommitIndex>commitIndex,说明是新的提交位点,回放日志,设置commitIndex =
min(leaderCommitIndex, index of last new entry)

备注:默认情况下,如果日志不匹配,会按logIndex逐条往前推进,直到找到match的位置,有一个简单的思路是,每次往前推进一个term,这样可以减少了网络交互,尽快早点match的位置,代价是可能传递了一些多余的日志。

关键词:日志连续一致性,多数派,leader日志不变更

快照流程

避免日志占满磁盘空间,需要定期对日志进行清理,在清理前需要做快照,这样新加入的节点可以通过快照+日志恢复。
快照属性:
1.最后一个已经提交的日志(termId,logIndex)
2.新的快照生成后,可以删除之前的日志和以前的快照。
删日志不能太快,否则,crash后的机器,本来可以通过日志恢复,如果日志不存在,需要通过快照恢复,比较慢。

leader发送快照流程

传递参数(leaderTermId, lastIndex, lastTerm, offset, data[], done_flag)
1.如果发现日志落后太远(超过阀值),则触发发送快照流程
备注:快照不能太频繁,否则会导致磁盘IO压力较大;但也需要定期做,清理非必要的日志,缓解日志的空间压力,另外可以提高follower追赶的速度。

follower接收快照流程

1.如果leaderTermId<currentTerm, 则返回
2.如果是第一个块,创建快照
3.在指定的偏移,将数据写入快照
4.如果不是最后一块,等待更多的块
5.接收完毕后,丢掉以前旧的快照
6.删除掉不需要的日志

集群配置变更

C(old): 旧配置
C(new): 新配置
C(old-new): 过渡配置,需要同时在old和new中达成多数派才行
原则:配置变更过程中,不会导致出现两个leader
二阶段方案:引入过渡阶段C(old-new)
约定:任何一个follower在收到新的配置后,就采用新的配置确定多数派。

变更流程

1.leader收到从C(old)切换到C(new)配置的请求
2.创建配置日志C(old-new),这条日志需要在C(old)和C(new)中同时达成多数派
3.任何一个follower收到配置后,采用的C(old-new)来确定日志是否达成多数派(即使C(old-new)这条日志还没达成多数派)
备注:1,2,3阶段只有可能C(old)节点成为leader,因为C(old-new)没有可能成为多数派。
4.C(old-new)日志commit(达成多数派),则无论是C(old)还是C(new)都无法单独达成多数派,即不会存在两个leader
5.创建配置配置日志C(new),广播到所有节点
6.同样的,任何一个follower收到配置后,采用的C(new)来确定日志是否达成多数派
备注:在4,5,6阶段,只有可能含有C(old-new)配置的节点成为leader。
7.C(new)配置日志commit后,则C(old-new)无法再达成多数派
8.对于不在C(new)配置的节点,就可以退出了,变更完成。
备注:在7,8阶段,只有可能含有C(new)配置成为leader。
所以整个过程中永远只会有一个leader。对于leader不在C(new)配置的情况,需要在C(new)日志提交后,自动关闭。

相关阅读
http://thesecretlivesofdata.com/raft/ (强烈推荐阅读,动画的形式化繁为简地讲解raft)
https://raft.github.io/
https://raft.github.io/raft.pdf
https://medium.com/@techgeek628/raft-understandable-distributed-consensus-987b3783d48a

Contents
  1. 1. 概念与术语
  2. 2. Leader选举
    1. 2.1. Leader流程
    2. 2.2. Candidate流程
    3. 2.3. Follower流程
  3. 3. 日志复制流程
    1. 3.1. leader流程
    2. 3.2. follower处理流程
  4. 4. 快照流程
    1. 4.1. leader发送快照流程
    2. 4.2. follower接收快照流程
  5. 5. 集群配置变更
    1. 5.1. 变更流程