Raft
Raft将问题分解和具体化:Leader统一处理变更操作请求,一致性协议的作用具化为保证节点间操作日志副本(log replication)一致,以term作为逻辑时钟(logical clock)保证时序,节点运行相同状态机(state machine)得到一致结果。Raft协议具体过程如下:
- Client发起请求,每一条请求包含操作指令
- 请求交由Leader处理,Leader将操作指令(entry)追加(append)至操作日志,紧接着对Follower发起AppendEntries请求、尝试让操作日志副本在Follower落地
- 如果Follower多数派(quorum)同意AppendEntries请求,Leader进行commit操作、把指令交由状态机处理
- 状态机处理完成后将结果返回给Client
1 leader选举
1.1 刚开始所有server启动都是follower状态
然后等待leader或者candidate的RPC请求、或者超时。
上述3种情况处理如下:
- leader的AppendEntries RPC请求:更新term和leader信息,当前follower再重新重置到follower状态
- candidate的RequestVote RPC请求:为candidate进行投票,如果candidate的term比自己的大,则当前follower再重新重置到follower状态
- 超时:转变为candidate,开始发起选举投票
1.2 candidate收集投票的过程
candidate会为此次状态设置随机超时时间,一旦出现在当前term中大家都没有获取过半投票即split votes,超时时间短的更容易获得过半投票。
candidate会向所有的server发送RequestVote RPC请求,请求参数见下面的官方图
votedFor是server保存的投票对象,一个server在一个term内只能投一次票。如果此时已经投过票了,即votedFor就不为空,那么此时就可以直接拒绝当前的投票(当然还要检查votedFor是不是就是请求的candidate)。
如果没有投过票:则对比candidate的log和当前server的log哪个更新,比较方式为谁的lastLog的term越大谁越新,如果term相同,谁的lastLog的index越大谁越新。
candidate统计投票信息,如果过半同意了则认为自己当选了leader,转变成leader状态,如果没有过半,则等待是否有新的leader产生,如果有的话,则转变成follower状态,如果没有然后超时的话,则开启下一次的选举。
2 log复制
2.1 请求都交给leader
一旦leader选举成功,所有的client请求最终都会交给leader(如果client连接的是follower则follower转发给leader)
2.2 处理请求过程
2.2.1 client请求到达leader
leader首先将该请求转化成entry,然后添加到自己的log中,得到该entry的index信息。entry中就包含了当前leader的term信息和在log中的index信息
2.2.2 leader复制上述entry到所有follower
来看下官方给出的AppendEntries RPC请求
从上图可以看出对于每个follower,leader保持2个属性,一个就是nextIndex即leader要发给该follower的下一个entry的index,另一个就是matchIndex即follower发给leader的确认index。
一个leader在刚开始的时候会初始化:
nextIndex=leader的log的最大index+1
matchIndex=0
然后开始准备AppendEntries RPC请求的参数
prevLogIndex=nextIndex-1
prevLogTerm=从log中得到上述prevLogIndex对应的term
然后开始准备entries数组信息
从leader的log的prevLogIndex+1开始到lastLog,此时是空的
然后把leader的commitIndex作为参数传给
leaderCommit=commitIndex
至此,所有参数准备完毕,发送RPC请求到所有的follower,follower再接收到这样的请求之后,处理如下:
- 重置HeartbeatTimeout
- 检查传过来的请求term和当前follower的term
Reply false if term < currentTerm
- 检查prevLogIndex和prevLogTerm和当前follower的对应index的log是否一致,
Reply false if log doesn’t contain an entry at prevLogIndex whose term
matches prevLogTerm
这里可能就是不一致的,因为初始prevLogIndex和prevLogTerm是leader上log的lastLog,不一致的话返回false,同时将该follower上log的lastIndex传送给leader
- leader接收到上述false之后