一、日志存储
每条日志存储内容如下:
term:领导人所在任期
利用操作内容:由客户端发送的申请,须要被复制状态机(replicated state machine)执行的命令,如上是一个 KV 零碎,每一次的操作是对某个 key 的内容。
二、日志状态
日志大略分以下几个状态:
1、初始化
即刚被退出到零碎中
2、被提交
如果一条日志被少数节点收到,则该日志会被转为被提交,即能够被利用到状态机。
3、已利用
即曾经利用到状态机了,能够返回给客户端了。
三、与日志相干的音讯
1、AppendEntries RPC
这个音讯由 Leader 收回,有 2 个作用:
A、将客户端发送的命令而产生的日志发送给 Follower,从而推动日志达到统一的状态;
B、心跳,表明集群中存在 Leader,不必发动选举;
第一个场景是承受客户端命令后收回,并且等大部分 Follower 接管到才返回给客户端;
第二个场景由 Leader 定时收回;
相干规定如下:
1、对于 Leader
- 于一个追随者来说,如果上一次收到的日志索引大于将要收到的日志索引(nextIndex):通过 AppendEntries RPC 将 nextIndex 之后的所有日志条目发送进来
- 如果发送胜利:将该追随者的 nextIndex 和 matchIndex 更新
- 如果因为日志不统一导致 AppendEntries RPC 失败:nextIndex 递加并且从新发送(5.3 节)
- 如果存在一个满足 N > commitIndex 和 matchIndex[i] >= N 并且 log[N].term == currentTerm 的 N,则将 commitIndex 赋值为 N
下面一条规定好了解,以下面的举例来说:
假如节点从上到下别离是 A、B、C、D、E,以后 Leader 是节点 A,后面说过 Leader 针对每一个 Follower 会记录 nextIndex 和 matchIndex,咱们假如最现实的状况,即 A 针对 B 记录的 nextIndex 是 9,matchIndex 是 8,所以心跳音讯中的 nextIndex 是 9,B 收到音讯会查看 nextIndex 之前的日志是否在本地存在,因 6 - 8 的日志不存在,因而返回 false,A 因而将 nextIndex 回退至 6 才匹配上,而后将 6 - 8 的日志发送给 B,并且将 matchIndex 和 nextIndex 也一并更新。
规定 2 比拟难了解:
如果存在一个满足 N > commitIndex 和 matchIndex[i] >= N 并且 log[N].term == currentTerm 的 N,则将 commitIndex 赋值为 N
重点是加粗的内容,说简略点以后 Leader 不能间接提交日志的 term 不为本人的,即不能间接提交后任的日志,举个例子:
这是 5 个节点的集群,服务器编号别离是 S1-S5,最下面是日志索引,每个框内的数字示意日志的 term,最底下的字母示意场景,别离为 a -e。
场景 a:S1 是 Leader,term 为 2,并且将 index 为 2 的日志复制到 S2 上;
场景 b:S1 挂了,S5 入选为 Leader,term 增长为 3,S5 在 index 为 2 的地位上接管到了新的日志;
场景 c:S5 挂了,S1 入选为 Leader,term 增长为 4,S1 将 index 为 2、term 为 2 的日志复制到了 S3 上,此时曾经满足过半数了。
问题就在场景 c:此时 term 为 4, 之前 term 为 2 的日志达到过半数了,S1 是提交该日志呢还是不提交 ?
如果 S1 提交的话,则 index 为 2、term 为 2 的日志就被利用到状态机中了,就不可吊销了;
此时 S1 如果挂了,来到场景 d,S5 是能够被选为 leader 的,因为依照之前的 log 比对策略来说,S5 的最初一个 log 的 term 是 3,比 S2、S3、S4 的最初一个 log 的 term 都大。
一旦 S5 被选举为 leader,即场景 d,S5 会复制 index 为 2、term 为 3 的日志到上述机器上,这时候就会造成之前 S1 曾经提交的 index 为 2 的地位被从新笼罩,因而违反了一致性。
如果 S1 不提交,而是等到 term4 中有过半的日志了,而后再将之前的 term 的日志一起提交,即处于 e 场景,S1 此时挂的话,S5 就不能被选为 leader 了,因为 S2、S3 的最初一个 log 的 term 为 4,比 S5 的 3 大,所以 S5 获取不到投票,进而 S5 就不可能去笼罩上述的提交。
总结下:Leader 不能间接提交后任的日志,哪怕后任日志曾经被少数节点收到了,而是等等以后任期的日志被大多数节点接管后做提交后,间接的提交了后任的日志。
四、其它技术细节
1、AppendEntries RPC 音讯参数
term:领导人的任期号
leaderId:领导人标识
prevLogIndex:前一条日志索引号
prevLogTerm:前一 条日志任期号
具体要复制的音讯
laderCommitIndex:leader 的 commitIndex
2、Follower 收到响应处理过程
首先是 term 的查看,这块是公共逻辑:
查看对方的 term 是否比本人小,如果是则返回本人的 term,并返回失败;
如果本人的 term 比对方大,则不论以后什么角色变为 Follower,并更新 term 为对方的 term,并增加日志;
如果 term 和本人相等,则只有 Follower、Candidate 才变为 Follower,更新 Term,并增加日志,对于 Leader 应该返回失败。
3、对于日志太大的问题
对于 1 个 7 *24 小时的服务,如果日志始终追加,最终磁盘空间必定不够的,有问题复原也太慢,这就生产了日照的需要,Raft 算法大设计的时候就思考到了这个问题,这个留到前面章节剖析。