Raft是工程上使用较为广泛的强一致性、去中心化、高可用的分布式协议。(在学术理论界最耀眼的还是Paxos,但是他比较难理解。)
Raft is a consensus algorithm for managing a replicated log.
raft 是一个共识算法,共识就是多个节点对莫个事情达成一致的看法,也就是说即使在部分节点故障,网络延时的情况下,也可以保证一致性。
> 在拜占庭将军的问题中:
>
> 假设将军中没有叛军,信使的信息可靠但有可能被暗杀的情况下,将军们如何达成一致性决定?
>
> Raft 的解决方案大概可以理解成 先在所有将军中选出一个大将军,
所有的决定由大将军来做。**选举环节**:比如说现在一共有3个将军 A, B, C,
每个将军都有一个**随机时间**的倒计时器,倒计时一结束,这个将军就会把自己当成大将军候选人,
然后派信使去问其他几个将军,能不能选我为总将军?假设现在将军A倒计时结束了,
他派信使传递选举投票的信息给将军B和C,如果将军B和C还没把自己当成候选人(倒计时还没有结束),
并且没有把选举票投给其他,他们把票投给将军A,信使在回到将军A时,将军A知道自己收到了足够的票数,
成为了大将军。在这之后,是否要进攻就由大将军决定,然后派信使去通知另外两个将军,
如果在一段时间后还没有收到回复(可能信使被暗杀),那就再重派一个信使,直到收到回复。
每一个节点拥有三种状态:
每一个节点服务器都会有一个倒计时(Election Timeout),时间随机在 150 ms 到 300 ms 之间。 但是有以下几种情况会重现设置 Timeout。
在一个节点倒计时结束后,他就妄图成为 Leader,于是 他的状态变为 Candidate 开始选举。 他就给其他几个节点发送选举请求。
其他四个节点返回成功,那么这个节点就变成了 Leader。 Leader 每隔一小段时间,就给所有的 Follower 发送一个 Heartbeat 用来检测和保持所有节点的状态。 Follower 收到 Leader 的 Heartbeat 之后重设 Timeout。
只要有一半的节点投了支持票,Candidate 才会被选举为 Leader(自己也算一票,每一个服务器每一轮只有一张选票。)
当第一轮的 Leader 由于种种原因宕机了。那么剩下的四个 Follower 将进行重新的选主。
此时 L2 已经产生,此时轮次已经变为第二轮。即使第一轮的 Leader 重新恢复,那么由于他的**轮次小于第二轮,所以他就是个弟弟,就要乖乖变成 Follower。**也就是上面说的 discobers server with highter term
。
就是有可能有冲突,也就是平票或者是票数不过半的情况下,在等下一轮重新选就是了。因为 计时器的随机性,在下一轮再次冲突的概率特别小。
Raft 在实际应用场景中的一致性更多的体现是在不同节点之间的数据一致性,也就是说在客户端发送请求到任何一个节点都能收到一个一致的返回,当一个节点出现故障之后,其他节点仍然可以用已有的数据正常的进行。 这也就是日志复制的目的。
复制日志步骤主要为(正常情况下):
一开始 Leader 收到了 sally 这个数据(红色表示未确认)
Leader 同步给 Follower(注意此时所有的数据还都是 Uncommitted)
Follower 收到数据后 返回 OK ,当 Leader 收到的 OK 超过半数时才会改为 Committed。
Leader 再次去同步所有的 Follower。(此时的 Leader 就可以把数据返回给客户端。)
即使在 Network Partition 的情况下,部分节点没法进行通信, Raft 也能保证这种情况下数据的一致性。
一开始有 5 个节点,由于 Network Partition 将节点分为两边,一边两个,一边是三个。
由于 L1 只能收到 F1 的OK 节点数为 2 个(包括自己)少于一半,那么 bob 这个数据就无法确认。那么此时 服务器会返回错误 给客户端。
同时另外一边 由于长时间没有收到 心跳检测,就开始了自己的选主活动。
客户端发送数据 “tom” 发到新的 Leader 上面,新的 Leader 通过正常的操作,可以确认这个数据。
当网络恢复时,整体状态如下。此时 “tom” 已经 committed 了,但是 “bob” Uncommitted
这时 L2(因为是第二轮,轮次高)进行全员广播,L1 自动降为 Follower。
由于 bob 没有得到确认。那么在收到新的 AppendEntries 请求时,就可以把 bob 给删除了,然后去同步 tom。
通过这么一个过程,就完成了在 Network Partition 情况下的复制日志,保证了数据的一致性。
Raft 增加了如下的两条限制以保证安全:
拥有最新的已提交的Log Entry的Follower才有资格成为Leader(也就是选举限制)
在Raft协议中,所有的日志条目都只会从Leader节点往Follower节点写入,且Leader节点上的日志只会增加,绝对不会删除或者覆盖。
这意味着Leader节点必须包含所有已经提交的日志,即能被选举为Leader的节点一定需要包含所有的已经提交的日志。因为日志只会从Leader向Follower传输,所以如果被选举出的Leader缺少已经Commit的日志,那么这些已经提交的日志就会丢失,显然这是不符合要求的.
这就是Leader选举的限制:能被选举成为Leader的节点,一定包含了所有已经提交的日志条目。
Leader只能推进commit index到当前任期号的已经复制到大多数服务器上的日志,旧任期号日志的提交要等到提交当前任期号的日志来间接提交(log index 小于 commit index的日志被间接提交);
(a) S1是Leader,并且部分地复制了index-2;
(b) S1宕机,S5得到S3、S4、S5的投票当选为新的Leader(
S2不会选择S5,因为S2的日志较S5新),并且在index-2写入到一个新的条目,
此时是term=3(注:之所以是term=3,是因为在term-2的选举中,
S3、S4、S5至少有一个参与投票,也就是至少有一个知道term-2,
虽然他们没有term-2的日志);
(c) S5宕机,S1恢复并被选举为Leader,并且开始继续复制日志
(也就是将来自term-2的index-2复制给了S3),此时term-2,
index-2已经复制给了多数的服务器,但是还没有提交;
(d) S1再次宕机,并且S5恢复又被选举为Leader
(通过S2、S3、S4投票,因为S2、S3、S4的term=4<5,
且日志条目(为term=2,index=2)并没有S5的日志条目新,所以可以选举成功),
然后覆盖Follower中的index-2为来自term-3的index-2;
(注:此时出现了,term-2中的index-2已经复制到三台服务器,还是被覆盖掉);
(e) 然而,如果S1在宕机之前已经将其当前任期(term-4)的条目都复制出去,
然后该条目被提交(那么S5将不能赢得选举,因为S1、S2、S3的日志term=4比S5都新)。
此时所有在前的条目都会被很好地提交。
到目前为止,我们都假设集群的配置(加入到一致性算法的服务器集合)是固定不变的。但是在实践中,偶尔是会改变集群的配置的,例如替换那些宕机的机器或者改变复制级别。尽管可以通过暂停整个集群,更新所有配置,然后重启整个集群的方式来实现,但是在更改的时候集群会不可用。另外,如果存在手工操作步骤,那么就会有操作失误的风险。为了避免这样的问题,我们决定自动化配置改变并且将其纳入到 Raft 一致性算法中来。
为了让配置修改机制能够安全,那么在转换的过程中不能够存在任何时间点使得两个领导人同时被选举成功在同一个任期里。不幸的是,任何服务器直接从旧的配置直接转换到新的配置的方案都是不安全的。一次性自动的转换所有服务器是不可能的,所以在转换期间整个集群存在划分成两个独立的大多数群体的可能性。
在上图中就会出现两个 Leader同时出现的情况。
新节点要加入的时候没有存储任何日志条目,如果该节点直接加入,可能要花一段时间来追赶日志。而这段时间可能无法响应client的请求。Raft解决该问题的方法是增加一个新的阶段,先将新的节点作为不计票的成员加入到集群。等到该新节点日志一致后再开始配置的更新。
一个配置切换的时间线。虚线表示已经被创建但是还没有被提交的条目,实线表示最后被提交的日志条目。领导人首先创建了 C-old,new 的配置条目在自己的日志中,并提交到 C-old,new 中(C-old 的大多数和 C-new 的大多数)。然后他创建 C-new 条目并提交到 C-new 中的大多数。这样就不存在 C-new 和 C-old 可以同时做出决定的时间点.
总结一下:
Raft原理动画推荐看下。