1. 概念

1.1. Raft协议

Raft 协议是一个为分布式系统提供强一致性的一种共识算法,它是为了替代复杂难懂的 Paxos 算法而生的。使用 Raft 协议的 etcd,可以确保集群状态的强一致性。这对于分布式系统来说是至关重要的,例如在 Kubernetes 这样的系统中,etcd 被用作保存集群的状态,强一致性能确保所有节点看到的状态都是一致的。

Raft协议是一种用于管理复制日志的一致性算法,主要用于解决分布式系统中的容错问题。它的主要作用包括以下几点:

  • 选举领导者:Raft协议通过投票过程选出一个领导者(Leader),所有的客户端请求都通过领导者进行协调和处理。
  • 容错恢复:一旦领导者宕机,Raft协议能通过重新选举产生新的领导者,使系统恢复正常运行。
  • 日志复制:领导者负责将自己的操作日志条目复制到其他的Follower节点,确保系统中的多个节点可以存储相同的数据。
  • 一致性保障:如果一个日志条目在领导者的日志中被提交,那么这个日志条目最终将存在于所有可用的Follower节点的日志中。

Raft协议的设计目标是易于理解和实现,它解决了分布式系统中的一致性问题,使得分布式系统具有更高的可用性和可靠性。

1.2. 角色状态

在 Raft 中,有三种角色:

  • 领导者(Leader)
  • 跟随者(Follower)
  • 候选者(Candidate)
任期内

在正常任期内,必须保证所有群众服从一个领袖领导。一个 Raft 集群中只能有一个 Leader,其他节点都是 Follower。Leader 负责处理所有的客户端交互,日志复制等,Follower 仅仅响应领导者的请求。

选举期

如果 Leader 宕机或者和集群其余的节点失去连接,那么就进入选举状态。就像民主大选,选举期间所有群众都能被选,此时所有节点都可以变为 Candidate,Candidate 通过投票的方式选出新的 Leader。

Raft 协议可以分为两个主要部分:

  • 领导人选举:用于在现有的 Leader 失败时,所有 Candidate 选择一个新的 Leader。
  • 日志复制:Leader 将客户端请求的命令以日志条目的形式复制到其他的 Follower 服务器上。

2. 选举

2.1. 选举流程

  1. 开始选举:在初始状态下,所有的节点都是 Follower(跟随者)。如果一个 Follower 在一段时间内没有接收到 Leader 的心跳,它就会把自己标记为 Candidate(候选人)并开始一次选举。首先,候选人会增加它的 term(任期)并投票给自己,然后它会向其他所有节点发起 RequestVote RPC 请求投票。
  2. 投票:收到 RequestVote 请求的 Follower 在确保投票的条件满足后会投票给这个候选人。投票的条件包括:候选人的 term 必须大于等于 Follower 的 term,并且候选人的日志要至少和自己一样新。这样能确保投票给的候选人有足够新的日志。
  3. 赢得选举:一旦候选人得到了大多数节点(超过半数)的投票,它就成为了新的 Leader。然后 Leader 会开始向其他所有节点发送心跳消息(AppendEntries RPC),以阻止其他节点开始新的选举。
  4. 选举失败和再次选举:如果选举过程中没有一个候选人得到了大多数节点的投票(例如在网络分区的情况下),那么选举就会失败。此时,每个节点都会随机等待一段时间后再开始新的选举。这个随机的等待时间可以防止选举的 "震荡"(即频繁、无结果的选举)。
  5. 新 Leader 的崭新任期:一旦选举成功,新的 Leader 就会开始它的工作。如果有新的日志条目需要复制,它会通过 AppendEntries RPC 来复制给其他节点。如果有旧的未提交的日志,它也会尽快提交这些日志并通知其他节点。

这就是 Raft 协议的选举过程。这个过程可以确保在任何时刻,集群中只有一个 Leader,并且这个 Leader 拥有最新的日志。这为分布式系统提供了强一致性的保证。

2.2. 唯一投票

在Raft协议中,一个节点在一次任期(term)内只能投一次票。这是为了防止同一候选者在一轮选举中获得多次投票,或者多个候选者在同一轮选举中都获得同一节点的投票,从而确保选举的公正性。

在进入选举投票时,每个节点都会先给自己投票,那会不会出现所有的节点都给自己投票呢?注意上述中 Candidate 和 Follower 的区别。

Follower 在一段时间内(超时时间)没有接收到 Leader 的心跳,它就会把自己标记为 Candidate,成为 Candidate 后才会给自己投票,并给其他节点发起请求投票。但每个节点的超时时间都是随机的,所以所有 Follower 节点进入 Candidate 状态的时间是不一样的。当第一个 Candidate 发送请求投票给其他节点时,其他节点基本还是 Follower 状态,还没有把票投给自己。

同样如果第一轮选举失败,要发起下一轮选举。每个节点还是经过随机的不同超时时间后,再进入 Candidate 状态。

2.3. 任期

在Raft协议中,任期被用作逻辑时钟,并用于解决冲突。每当选举开始时,候选者都会增加它们的任期。这样可以确保每一轮选举都有一个唯一的标识,同时也能防止过期的选举结果影响新的选举。

如果第一轮选举失败了,第二轮选举开始时,候选者依然会首先增加它们的任期。例如,假设一个节点在第一轮选举中没有获得足够的投票,然后开始第二轮选举。此时,如果它没有增加其任期,那么就可能出现一个问题:这个节点可能同时在第一轮和第二轮选举中都是候选者。这将导致混乱,因为其他节点可能不知道他们应该对哪一轮选举进行投票。

3. 日志复制

3.1. 日志复制

当客户端发送一个请求来修改状态机时,这个请求会被首先发送到领导者(Leader)。领导者会把这个操作作为一个新的日志条目添加到自己的日志中,此时这个日志条目的状态为未提交(uncommitted)。然后,领导者会把这个日志条目发送给所有的 Follower 节点。

Follower 节点收到日志条目后,会把它添加到自己的日志中,并向领导者回复已经接收到了这个日志条目。当领导者收到了 大多数节点(包括领导者自己)已经写入了这个日志条目的回复后,领导者会认为这个日志条目已经提交(committed)。

然后领导者会向所有Follower节点发送一条消息,告诉它们这个日志条目已经被提交。收到这个消息的Follower节点会把这个日志条目标记为已提交。

3.2. 一致性保障

Raft协议通过几种方式来保证一致性:

  • Leader 只会增加新的日志条目,不会删除或修改已经存在的日志条目。
  • Leader 会保持所有 Follower 节点的日志与自己的日志一致。如果某个 Follower 节点的日志落后于领导者,领导者会发送一致性检查请求,让 Follower 节点的日志追赶上来。
  • 如果一个日志条目在 Leader 的日志中被提交,那么这个日志条目最终将存在于所有可用的 Follower 节点的日志中。

这些机制确保了Raft协议可以在分布式环境中实现日志复制并保持一致性,即使在面临网络分区和其他故障的情况下。

4. ZAB协议

4.1. 概述

ZAB 协议主要是用在 ZooKeeper 集群中。同 Raft协议一样,都是为了替代复杂难懂的 Paxos 算法,二者的相似度很高。

ZAB协议主要包括两种模式:广播模式和恢复模式。

  • 广播模式:在系统正常运行时,所有的数据更新操作都由一个领导者节点(Leader)发起,然后通过原子广播方式传递到所有的追随者节点(Follower)。
  • 恢复模式:当系统中的领导者节点发生故障,或者系统新启动时,ZAB协议会切换到恢复模式。在恢复模式下,节点会选举新的领导者,并且确保所有节点的状态达到一致后,才会切换回广播模式。新的领导者是最后一个提出提案的节点,如果多个节点同时提议,则选择ID最大的那个。

ZAB协议与其他的一致性协议(如Paxos,Raft)类似,都是通过选举领导者,然后由领导者来处理所有的数据更新请求,以保证所有的副本节点能够达到一致的状态。

4.2. 选举

ZAB协议的选举过程主要发生在恢复模式中,一般在系统启动或者领导者节点故障时触发,主要分以下几步:

  • 开始选举:每个节点首先投给自己一票,并将自己的编号和最后一条已经提交的事务的ZXID(Zookeeper Transaction ID,包含了事务的epoch和计数器)发送给其他所有节点,表示它推举自己成为领导者。
  • 收集选票:接收到其他节点的投票信息后,每个节点会比较其他节点的ZXID和自己的ZXID。如果其他节点的ZXID更高(epoch或者计数器更大),或者ZXID相同但是节点编号更大,那么就会将票投给这个节点。
  • 计票:每个节点收集到超过半数的投票(包括自己的)后,就会认为选举完成,选出的领导者就是得票最多的节点。如果有多个节点的票数相同,就选择ZXID最大的节点,如果还是相同,就选择节点编号最大的。
  • 领导者确认:选举完成后,新的领导者会向所有节点发送领导者确认消息,其他节点收到确认消息后,会向领导者发送已经接受领导者的消息。
  • 新领导者工作:当领导者收到大多数节点的确认消息时,就可以开始提供服务,处理客户端的请求,从而结束了选举过程。

ZAB协议的选举过程是为了在领导者节点故障时快速选出新的领导者,恢复系统的正常运行,同时还确保了系统的一致性。

4.3. 与Raft区别

通过选举流程来看,二者差别不大,它们在领导者选举的过程中主要区别在于:

  • 选举条件:在Raft中,如果 Follower 在一段时间内没有收到 Leader 的心跳信息,就会主动进入候选人状态并启动新一轮选举,原因可能只是 Leader 节点繁忙而无法及时响应等情况。而在ZAB除了系统启动时,只有等到 Leader 故障,无法提供服务时才会进行选举。Raft更偏向于积极选举,只要有可能就尝试选举新的领导者,而ZAB更倾向于保持现状,只有在确定领导者无法提供服务时才触发选举。
  • 投票规则:Raft 中,每个节点只能投票一次,并且投票给第一个请求投票的候选人。而在 ZAB 协议中,节点可能会改变自己的投票,投给 ZXID 更大的节点。所以 ZAB 不存在随机超时时间,虽然第一票都会投给自己,但后续如果决定其他节点的 ZXID 更大,就修改自己的选票。
  • 票数要求:在Raft 中,候选人在获得超过半数的投票后成为 Leader。在 ZAB 中,新的领导者被选出后,还需要得到超过半数的节点的确认才能开始工作。
  • 决定因素:在Raft中,选举过程严格按照轮次进行,每轮选举只能选出一位Leader,选出 Leader 很大程度取决于随机的超时时间和日志最新。而在 ZAB 中,选举的结果主要取决于节点的 ZXID 以及节点编号。ZXID最大的节点通常是最近处理过事务,数据最新的节点。

5. 写入流程区别

5.1. raft写入流程

Raft 算法的写入流程

  1. 客户端请求:客户端将写请求发送到 Raft 集群中的领导者节点。
  2. 日志追加:领导者将请求作为一个新的日志条目追加到其本地日志中,并分配一个唯一的日志索引。
  3. 提案广播:领导者将该日志条目以 AppendEntries RPC 的形式广播给所有跟随者。
  4. 跟随者接收并写入日志:跟随者接收到 AppendEntries RPC 后,将日志条目追加到本地日志,并返回确认(ACK)消息。
  5. 多数确认:领导者等待大多数(超过半数)跟随者的确认一旦收到确认,领导者将该日志条目标记为已提交(committed)。
  6. 应用状态机:领导者将已提交的日志条目应用到状态机,并将结果返回给客户端。
  7. 通知跟随者:领导者通过后续的 AppendEntries RPC 将日志条目标记为已提交,通知所有跟随者以确保提交状态一致。

5.2. zab写入流程

Zab 协议的写入流程

  1. 客户端请求:客户端将写请求发送到 ZooKeeper 集群中的领导者节点。
  2. 提案生成:领导者将请求作为一个提案(Proposal)生成,并分配一个唯一的提案 ID。
  3. 提案广播:领导者将提案广播给所有跟随者。
  4. 跟随者接收并写入日志:跟随者接收到提案后,将提案写入本地事务日志,并返回确认(ACK)消息。
  5. 多数确认:领导者等待大多数(超过半数)跟随者的确认,并进入预提交状态。
  6. 预提交广播:领导者向所有跟随者广播预提交消息,表示该提案即将提交。
  7. 跟随者预提交确认:跟随者接收到预提交消息,将提案标记为已预提交,并写入日志。
  8. 正式提交:领导者在收到多数预提交确认后,进入正式提交状态,并向所有跟随者广播提交消息。
  9. 日志应用:跟随者接收到提交消息,将提案应用到状态机,并将结果返回给领导者。
  10. 客户端响应:领导者向客户端返回操作结果,表示写入操作已完成。

5.3. 对比

基于流程可见,二者在写入流程的大多数过程中都是相同的,核心区别在于。zab协议多了“预提交”的步骤,相比较而言更加复杂一些。

zab协议预提交和正式提交阶段增加了额外的消息交互和确认步骤。因此 Raft 相较于 Zab,在高并发写入场景中通常表现出较高的效率和较低的延迟。


KerryWu
641 声望159 粉丝

保持饥饿


引用和评论

0 条评论