如何保证分布式系统数据的一致性

分布式系统CAP理论

此章节重点介绍:C/一致性(分布式一致性协议实现算法Raft)

Raft算法的集群中的三种角色:

  • Leader(领袖)
  • Follower(群众)
  • Candidate(候选人)

每个节点都会且只能处于以上三种角色中的一种

算法的主体大致可分为2个过程 :1、选举 2、日志同步(数据同步)

选举

选举和现实社会中的民主选举制度很像,刚开始没有 Leader,所有集群中的参与者都是 Follower, 当 Follower 超过选举超时时间 ( election timeout ) 没收到来自 Leader 的心跳报文,则成为 Candidate,增加任期 ( Term ) 并向其他节点发起新的选举请求。接收到请求的节点如果还没投票,则投票给该节点,并重置自己的超时时间。如果某个 Candidate 节点接收到的选票的个数占大多数(超过 1/2), 它就会成为 Leader 节点,这就是所谓的 Leader election 的过程。每隔一定时间向 Follower 发送心跳报文来维持自己的 『统治』地位。

集群中的每个节点都处于以上三种角色中的一种,其状态转换图可以总结如下:

 

 

下面通过几个例子来帮助我们理解这个选举过程:

1. 可以正常选举出 Leader 的例子 :

 

在此例子中,一共有三个节点,复现流程如下:

  • 节点 B 首先出现心跳超时,然后变成 Candidate 角色。
  • 节点 B 首先把自己宝贵的一票投给自己,然后请求其他节点也将赞同票投给自己。
  • 节点 A 和 C 此时还未出现超时,仍然处于 Follower 角色,接收到请求后,投票给了 节点 B。
  • 节点 B 接收到的赞同票超过半数,成为 Leader 节点。

2. 平分选票的情况

由于 Raft 协议并不要求集群中的节点个数为奇数,于是在四个节点的集群中,可能出现如下的情况:

 

复现流程如下:

  • 节点 B 和 C 几乎同时心跳超时,成为 Candidate 角色。
  • 节点 B 和 C 均将自己的赞同票投给自己。
  • 节点 A 投给了节点 B,而 节点 D 则投给了节点 C。
  • 出现两个 Candidate 的选票相同的情况。

当出现这种情况的时候,没有任何一个节点接收到的赞同票超过半数,于是此选举过程不会有 Leader 角色出现。大家各自等待超时,然后开启下一轮的选举流程。

疑问:那有没有可能在下一轮的选举过程依然出现平分选票的情况?答案是有的,可是 Raft 有一个机制可以避免此种情况持续发生: 每个节点的超时时间都是随机的 ,这样就可以尽量避免多次出现以上的情况。

 

 

3. 选举流程要点总结

「1」Leader 角色通过发送心跳报文来维护自己的统治地位。「2」Follower 角色接收不到心跳报文,则超时开始下一轮的选举。「3」每个节点的超时时间都是随机的。「4」成为 Leader 节点需要赞同票超过半数。「5」选举结束,所有除 Leader 的候选人又变回 Follower 服从 Leader。

日志同步

当选举完成后,客户端进行的操作都要通过 Leader 来进行。Leader 接受到客户端发来的操作请求后,首先记录到日志中,此时为 uncommitted 状态。然后在下一个心跳报文中通知所有 Follower,当大多数 Follower 响应 ACK 后,Leader 响应客户端,进入 committed 阶段,并向 Follower 发送 commit 通知应用( apply )操作。

日志同步流程如下:

 

上图中的序号对应一个存储的步骤, 日志同步 流程总结如下:「1」client 给 Leader 发送一个操作请求,假设为 SET X=8。「2」Leader 会把 SET X=8 这个操作记录到本地 log,此时为 uncommited 状态,同时将这个操作发送给所有的 Follower。「3」Follower 接收到操作请求后,也将 SET X=8 这个操作记录到本地 log(uncommited 状态),然后给 Leader 回复 ACK 信息。「4」Leader 节点当接收到的 ACK 超过半数后,给 client 回复 ACK 信息,进入 commited 阶段。「5」Leader 节点首先应用自己的 log,然后将 commit 消息发送给 Follower, 让 Follower 也 应用自己存储的日志信息。

Raft 中的异常处理

我们已经介绍了 Raft 算法的正常处理流程,然而现实总是很骨感,会出现各种异常的情况。client 和 Leader 之间的通信异常,不会影响到集群内部状态的一致性,不再赘述;如果在一个任期内,Follower 角色宕机,处理流程比较简单。其呈现出来的现象就是 步骤「3」异常,然而只要 Leader 能够接收到超过半数的 ACK,就不会影响到正常的存储流程。但是 Leader 角色会针对该 Follower 节点,持续进行步骤 「2」 的动作,直到接收到步骤 「3」的回应信息或者我们把该 Follower 节点从集群中拿掉;Raft 协议强依赖 Leader 节点的可用性来确保集群数据的一致性。下面重点介绍在各种情况下 Leader 节点出现故障时,Raft 协议是如何保障数据一致性的。

1. 写操作 到达 Leader 节点,但尚未同步到 Follower 节点

此刻集群的状态如下,用方框中的灰色表示当前处于 uncommited 状态。

 

 

如果集群在此状态,Leader 节点宕机,则 Raft 的处理流程如下:「1」Follower 节点超时,然后选举出新一任期的 Leader,然后它并没有处于 uncommited 状态的日志。「2」Client 节点由于 Ack 超时,可安全发起重试。「3」原来的 Leader 节点恢复后以 Follower 角色重新加入集群。并从当前任期的新 Leader 处同步数据,可能会覆盖原来处于 uncommited 状态的日志。

2. 写操作 到达 Leader 节点,且将写操作同步到 Follower 节点,但还未向 Leader 回复 ACK 信息。

此刻集群的状态如下,用方框中的灰色表示当前处于 uncommited 状态。

 

如果集群在此状态,Leader 节点宕机,则 Raft 的处理流程如下:「1」Follower 节点超时,然后选举出新一任期的 Leader,并且它存在处于 uncommited 状态的日志。「2」新的 Leader 节点假装没有刚才的选举过程,继续从步骤(2)开始执行,以完成数据的提交。「3」Client 节点由于 ACK 超时,可安全发起重试。「4」原来的 Leader 节点恢复后以 Follower 角色重新加入集群。并从当前任期的新 Leader 处同步数据。

此时,在上面步骤「1」中选举新的 Leader 节点时,需要满足一个限制条件,即新的 Leader 节点需要具有最新的日志信息,所谓的最新是通过任期和日志的偏移量两个参数来判定的,这个限制是为了解决只有部分 Follower 节点接收到写操作请求的情况。

3. Leader 节点接收到写操作的 ACK 信息,处于 commited 状态,然而 Follower 处于 uncommited 状态。

此种情况是由于,在步骤(5)执行过程中,Leader 宕机导致的,此刻集群的状态如下,用方框中的灰色表示当前处于 uncommited 状态,用方框中的黑色表示当前处于 commited 状态,同时圆圈中的值表示操作已经生效。

 

 

 

对于此种情况,Raft 协议的处理流程和上面情况2的处理流程是一样的。

4. 脑裂

由于网络故障,产生分区将原先的 Leader 节点和 Follower 节点分隔开,Follower 收不到 Leader 的心跳将发起选举,产生新的 Leader,这时就产生了双 Leader,这就是所谓的脑裂。这种情况下某些 Leader 由于获取不到大多数的投票,所以数据永远不会提交成功。当网络故障恢复后,旧的 Leader 发现有 Term 更新的 Leader 存在,则自动降级为 Follower 并从最新的 Leader 同步数据达成集群一致。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值