继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

分布式一致性协议综述(下)

2022-08-01 10:17:244464浏览

大能老师

1实战 · 9手记 · 6推荐
TA的实战

Raft协议

Paxos 是论证了一致性协议的可行性,但是论证的过程据说晦涩难懂,缺少必要的实现细节,而且工程实现难度比较高广为人知实现只有 zk 的实现 zab 协议。

Paxos协议的出现为分布式强一致性提供了很好的理论基础,但是Paxos协议理解起来较为困难,实现比较复杂。

然后斯坦福大学RamCloud项目中提出了易实现,易理解的分布式一致性复制协议 Raft。Java,C++,Go 等都有其对应的实现

之后出现的Raft相对要简洁很多。
引入主节点,通过竞选。
节点类型:Follower、Candidate 和 Leader

Leader 会周期性的发送心跳包给 Follower。每个 Follower 都设置了一个随机的竞选超时时间,一般为 150ms~300ms,如果在这个时间内没有收到 Leader 的心跳包,就会变成 Candidate,进入竞选阶段。

基本名词

  • 节点状态
    • Leader(主节点):接受 client 更新请求,写入本地后,然后同步到其他副本中
    • Follower(从节点):从 Leader 中接受更新请求,然后写入本地日志文件。对客户端提供读请求
    • Candidate(候选节点):如果 follower 在一段时间内未收到 leader 心跳。则判断 leader 可能故障,发起选主提议。节点状态从 Follower 变为 Candidate 状态,直到选主结束
  • termId:任期号,时间被划分成一个个任期,每次选举后都会产生一个新的 termId,一个任期内只有一个 leader。termId 相当于 paxos 的 proposalId。
  • RequestVote:请求投票,candidate 在选举过程中发起,收到 quorum (多数派)响应后,成为 leader。
  • AppendEntries:附加日志,leader 发送日志和心跳的机制
  • election timeout:选举超时,如果 follower 在一段时间内没有收到任何消息(追加日志或者心跳),就是选举超时。

两个基本过程

角色 描述
Leader选举 每个candidate随机经过一定时间都会提出选举方案,最近阶段中的票最多者被选为leader
同步1og(复制日志) leader会找到系统中log(各种事件的发生记录)最新的记录,并强制所有follow来刷新到这个记录。

Leader选举

针对简化版拜占庭将军问题,Raft解决方案

假设将军中没有叛军,信使的信息可靠但有可能被暗杀的情况下,将军们如何达成一致性决定?

Raft 的解决方案大概可以理解成 先在所有将军中选出一个大将军,所有的决定由大将军来做。选举环节:比如说现在一共有3个将军 A, B, C,每个将军都有一个随机时间的倒计时器,倒计时一结束,这个将军就会把自己当成大将军候选人,然后派信使去问其他几个将军,能不能选我为总将军?假设现在将军A倒计时结束了,他派信使传递选举投票的信息给将军B和C,如果将军B和C还没把自己当成候选人(倒计时还没有结束),并且没有把选举票投给其他,他们把票投给将军A,信使在回到将军A时,将军A知道自己收到了足够的票数,成为了大将军。在这之后,是否要进攻就由大将军决定,然后派信使去通知另外两个将军,如果在一段时间后还没有收到回复(可能信使被暗杀),那就再重派一个信使,直到收到回复。

正常情况下的leader选举

图片描述

① 5个节点一开始的状态都是 Follower,termId=1.
图片描述

② 在一个节点倒计时结束 (Timeout) 后,这个节点的状态变成 Candidate 开始选举,它给其他几个节点发送选举请求 (RequestVote)
图片描述

③ 四个节点都返回成功,这个节点的状态由 Candidate 变成了 Leader,并在每个一小段时间后,就给所有的 Follower 发送一个 Heartbeat 以保持所有节点的状态,Follower 收到 Leader 的 Heartbeat 后重设 Timeout。

这是最简单的选主情况,只要有超过一半的节点投支持票了,Candidate 才会被选举为 Leader,5个节点的情况下,3个节点 (包括 Candidate 本身) 投了支持就行。

故障时的leader选举

图片描述

① Leader 出故障挂掉了,其他四个 Follower 将进行重新选主。
图片描述

② 4个节点的选主过程和5个节点的类似,选出一个新的 Leader
图片描述

③ 原来的 Leader 恢复了又重新加入了,这个时候怎么处理?在 Raft 里,第几轮选举是有记录的,重新加入的 Leader 是第一轮选举 (Term 1) 选出来的,而现在的 Leader 则是 Term 2,所有原来的 Leader 会自觉降级为 Follower

多个Candidate情况下的选主

图片描述

假设一开始有4个节点,都还是 Follower。有两个 Follower 同时 Timeout,都变成了 Candidate 开始选举,分别给另外的 Follower 发送了投票请求。
图片描述

两个 Follower 分别返回了ok,这时两个 Candidate 都只有2票(含自己一票),要3票才能被选成 Leader。两个 Candidate 会分别给另外一个还没有给自己投票的 Follower 发送投票请求。但是因为 Follower 在这一轮选举中,都已经投完票了,所以都拒绝了他们的请求。所以在 Term 2 没有 Leader 被选出来。

这时,两个节点的状态是 Candidate,两个是 Follower,但是他们的倒计时器仍然在运行,最先 Timeout 的那个节点会进行发起新一轮 Term 3 的投票。
图片描述

两个 Follower 在 Term 3 还没投过票,所以返回 OK,这时 Candidate 一共有三票,被选为了 Leader。如果 Leader Heartbeat 的时间晚于另外一个 Candidate timeout 的时间,另外一个 Candidate 仍然会发送选举请求。
图片描述

两个 Follower 已经投完票了,拒绝了这个 Candidate 的投票请求。Leader 进行 Heartbeat, Candidate 收到后状态自动转为 Follower,完成选主。

以上是 Raft 最重要活动之一选主的介绍,以及在不同情况下如何进行选主。

复制日志 Log Replication

正常情况下复制日志

Raft 在实际应用场景中的一致性更多的是体现在不同节点之间的数据一致性,客户端发送请求到任何一个节点都能收到一致的返回,当一个节点出故障后,其他节点仍然能以已有的数据正常进行。在选主之后的复制日志就是为了达到这个目的。

图片描述

① 客户端发送请求给 Leader,储存数据 “Hello”,Leader 先将数据写在本地日志,这时候数据还是 Uncommitted (还没最终确认,浅灰表示)

图片描述

② Leader 给两个 Follower 发送 AppendEntries 请求,数据在 Follower 上没有冲突,则将数据暂时写在本地日志,Follower 的数据也还是 Uncommitted。

图片描述

③ Follower 将数据写到本地后,返回 OK。Leader 收到后成功返回,只要收到的成功的返回数量超过半数 (包含Leader),Leader 将数据 “sally” 的状态改成 Committed。( 这个时候 Leader 就可以返回给客户端了)

图片描述

④ Leader 再次给 Follower 发送 AppendEntries 请求,收到请求后,Follower 将本地日志里 Uncommitted 数据改成 Committed。这样就完成了一整个复制日志的过程,三个节点的数据是一致的。

Network Partition情况下进行复制日志

在 Network Partition 的情况下,部分节点之间没办法互相通信,Raft 也能保证在这种情况下数据的一致性。

图片描述

一开始有 5 个节点处于同一网络状态下。Network Partition 将节点分成两边,一边有两个节点,一边三个节点。

图片描述

两个节点这边已经有 Leader 了,来自客户端的数据 “world” 通过 Leader 同步到 Follower。因为只有两个节点,少于3个节点,所以 “world” 的状态仍是 Uncommitted。所以在这里,服务器会返回错误给客户端。

图片描述

另外一个 Partition 有三个节点,进行重新选主。客户端数据 “Raft” 发到新的 Leader,通过和上节网络状态下相似的过程,同步到另外两个 Follower。

图片描述

因为这个 Partition 有3个节点,超过半数,所以数据 “Raft” 都 Commit 成功了。

图片描述

网络状态恢复,5个节点再次处于同一个网络状态下。但是这里出现了数据冲突 “world" 和 “Raft"
图片描述

三个节点的 Leader 广播 AppendEntries。
图片描述

图片描述

两个节点 Partition 的 Leader 自动降级为 Follower,因为这个 Partition 的数据 “world” 没有 Commit,返回给客户端的是错误,客户端知道请求没有成功,所以 Follower 在收到 AppendEntries 请求时,可以把 “world“ 删除,然后同步 ”Raft”,通过这么一个过程,就完成了在 Network Partition 情况下的复制日志,保证了数据的一致性。

ZAB协议

概述

Google 的粗粒度锁服务 Chubby 的设计开发者 Burrows 曾经说过:“所有一致性协议本质上要么是 Paxos 要么是其变体”。Paxos 虽然解决了分布式系统中,多个节点就某个值达成一致性的通信协议。但是还是引入了其他的问题。由于其每个节点,都可以提议提案,也可以批准提案。当有三个及以上的 proposer 在发送 prepare 请求后,很难有一个 proposer 收到半数以上的回复而不断地执行第一阶段的协议,在这种竞争下,会导致选举速度变慢

所以 zookeeper 在 paxos 的基础上,提出了 ZAB 协议,本质上是,只有一台机器能提议提案(Proposer),而这台机器的名称称之为 Leader 角色。其他参与者扮演 Acceptor 角色。为了保证 Leader 的健壮性,引入了 Leader 选举机制。

ZAB协议还解决了这些问题

  1. 在半数以下节点宕机,依然能对台提供服务
  2. 客户端所有的写请求,交由 Leader 来处理。写入成功后,需要同步给所有的 follower 和 observer
  3. leader 宕机,或者集群重启。需要确保已经再 Leader 提交的事务最终都能被服务器提交,并且确保集群能快速回复到故障前的状态

ZAB协议 基本概念

基本名词

  • 数据节点(dataNode):zk 数据模型中的最小数据单元,数据模型是一棵树,由斜杠( / )分割的路径名唯一标识,数据节点可以存储数据内容及一系列属性信息,同时还可以挂载子节点,构成一个层次化的命名空间。
  • 事务及 zxid:事务是指能够改变 Zookeeper 服务器状态的操作,一般包括数据节点的创建与删除、数据节点内容更新和客户端会话创建与失效等操作。对于每个事务请求,zk 都会为其分配一个全局唯一的事务 ID,即 zxid,是一个 64 位的数字,高 32 位表示该事务发生的集群选举周期epoch(集群每发生一次 leader 选举,值加 1),低 32 位表示该事务在当前选择周期内的递增次序(leader 每处理一个事务请求,值加 1,发生一次 leader 选择,低 32 位要清 0)。
  • 事务日志:所有事务操作都是需要记录到日志文件中的,可通过 dataLogDir 配置文件目录,文件是以写入的第一条事务 zxid 为后缀,方便后续的定位查找。zk 会采取“磁盘空间预分配”的策略,来避免磁盘 Seek 频率,提升 zk 服务器对事务请求的影响能力。默认设置下,每次事务日志写入操作都会实时刷入磁盘,也可以设置成非实时(写到内存文件流,定时批量写入磁盘),但那样断电时会带来丢失数据的风险。
  • 事务快照:数据快照是 zk 数据存储中另一个非常核心的运行机制。数据快照用来记录 zk 服务器上某一时刻的全量内存数据内容,并将其写入到指定的磁盘文件中,可通过 dataDir 配置文件目录。可配置参数 snapCount,设置两次快照之间的事务操作个数,zk 节点记录完事务日志时,会统计判断是否需要做数据快照(距离上次快照,事务操作次数等于snapCount/2~snapCount 中的某个值时,会触发快照生成操作,随机值是为了避免所有节点同时生成快照,导致集群影响缓慢)。

核心角色

  • leader:系统刚启动时或者 Leader 崩溃后正处于选举状态;
  • follower:Follower 节点所处的状态,Follower 与 Leader 处于数据同步阶段;
  • observer:Leader 所处状态,当前集群中有一个 Leader 为主进程。

节点状态

  • LOOKING:节点正处于选主状态,不对外提供服务,直至选主结束;
  • FOLLOWING:作为系统的从节点,接受主节点的更新并写入本地日志;
  • LEADING:作为系统主节点,接受客户端更新,写入本地日志并复制到从节点

图片描述

Zookeeper 集群的模式

Zookeeper 集群的模式包括两种基本的模式:崩溃恢复消息广播

崩溃恢复模式

当整个集群启动过程中,或者当 Leader 服务器出现网络中弄断、崩溃退出或重启等异常时,Zab协议就会 进入崩溃恢复模式,选举产生新的Leader。

一但出现崩溃,会导致数据不一致,ZAB的崩溃恢复开始起作用。有如下两个确保:

  1. ZAB协议需要确保已经在Leader提交的事务最终被所有服务器提交。
  2. ZAB协议需要确保丢弃只在Leader服务器上被提出的事务。

针对上两个要求,如果Leader选举算法保证新选举出来的Leader服务器拥有集群中所有机器最高编号(ZXID最大)的事务Proposal,那么就能保证新的Leader 一定具有已提交的所有提案,更重要是,如果这么做,可以省去Leader服务器检查Proposal的提交和丢弃工作的这一步。

一旦Leader服务器出现崩溃,或者说网络原因导致Leader服务器失去了与过半的Follower的联系,那么就会进入崩溃恢复模式。为了保证程序的正常运行,整个恢复过程后需要选举一个新的Leader服务器。因此,ZAB协议需要一个高效可靠的Leader选举算法,从而确保能够快速的选举出新的Leader。同时,新的Leader选举算法不仅仅需要让Leader自己知道其自身已经被选举为Leader,同时还需要让集群中所有的其他机器也能够快速的感知选举产生的新的Leader服务器。

ZAB协议规定了如果一个事务Proposal在一台机器上被处理成功,那么应该在所有的机器上都被处理成功,哪怕机器出现崩溃。

消息广播模式

当新的Leader出来了,同时,已有过半机器完成同步之后,ZAB协议将退出恢复模式。进入消息广播模式。这时,如果有一台遵守Zab协议的服务器加入集群,因为此时集群中已经存在一个Leader服务器在广播消息,那么该新加入的服务器自动进入恢复模式:找到Leader服务器,并且完成数据同步。同步完成后,作为新的Follower一起参与到消息广播流程中。

如果集群中其他机器收到客户端事务请求后,那么会先转发Leader服务器,由Leader统一处理。

  1. 在zookeeper集群中,数据副本的传递策略就是采用消息广播模式。zookeeper中数据副本的同步方式与二段提交相似,但是却又不同。二段提交要求协调者必须等到所有的参与者全部反馈ACK确认消息后,再发送commit消息。要求所有的参与者要么全部成功,要么全部失败。二段提交会产生严重的阻塞问题。
  2. Zab协议中 Leader 等待 Follower 的ACK反馈消息是指“只要半数以上的Follower成功反馈即可,不需要收到全部Follower反馈”
  3. 整个过程中,Leader为每个事务请求生产对应的Proposal,在广播前,为这个事务分配一个全局唯一ID,为ZXID(事务ID),必须按照递增的事务顺序进行处理。

ZAB协议中涉及的二阶段提交和2pc有所不同。在ZAB协议的二阶段提交过程中,移除了中断逻辑,所有Follower服务器要么正常反馈Leader提出的事务Proposal,要么就抛弃Leader服务器。ZAB协议中,只要集群中过半的服务器已经反馈ACK,就开始提交事务了,不需要等待集群中所有的服务器都反馈响应。这种模型是无法处理Leader服务器崩溃退出而带来的数据不一致问题的,因此在ZAB协议中添加了另一个模式,即采用崩溃恢复模式来解决这个问题。此外,整个消息广播协议是基于具有FIFO特性的TCP协议来进行网络通信的,因此能够很容易保证消息广播过程中消息接受与发送的顺序性。

在整个消息广播过程中,Leader服务器会为每个事务请求生成对应的Proposal来进行广播,并且在广播事务Proposal之前,Leader服务器会首先为这个事务分配一个全局单调递增的唯一ID,我们称之为事务ID(即ZXID)。由于ZAB协议需要保证每一个消息严格的因果关系,因此必须将每一个事务Proposal按照其ZXID的先后顺序来进行排序与处理。

在消息广播过程中,Leader服务器会为每一个Follower服务器各自分配一个单独的队列,然后将需要广播的事务Proposal依次放入这些队列中,并且根据FIFO策略进行消息发送。每一个Follower服务器在接受到这个事务Proposal之后,都会首先将其以事务日志的形式写入到本地磁盘中去,并且在成功写入后反馈给Leader服务器一个ACK响应。当Leader服务器接收到超过半数Follower的ACK响应后,就会广播一个Commit消息给所有Follower服务器以通知其将事务进行提交,同时Leader自身也会完成事务的提交,而每一个Follower服务器收到Commit消息之后,也会完成对事务的提交。

ZAB的两种基本模式?

崩溃恢复:在正常情况下运行非常良好,一旦Leader出现崩溃或者由于网络原因导致Leader服务器失去了与过半Follower的联系,那么就会进入崩溃恢复模式。为了程序的正确运行,整个恢复过程后需要选举出一个新的Leader,因此需要一个高效可靠的选举方法快速选举出一个Leader。

消息广播:类似一个两阶段提交过程,针对客户端的事务请求, Leader服务器会为其生成对应的事务Proposal,并将其发送给集群中的其余所有机器,再分别收集各自的选票,最后进行事务提交。

哪些情况会导致ZAB进入恢复模式并选取新的Leader?

启动过程或Leader出现网络中断、崩溃退出与重启等异常情况时。

当选举出新的Leader后,同时集群中已有过半的机器与该Leader服务器完成了状态同步之后,ZAB就会退出恢复模式。

ZAB协议 常见的误区

  • 写入节点后的数据,立马就能被读到,这是错误的zk 写入是必须通过 leader 串行的写入,而且只要一半以上的节点写入成功即可。而任何节点都可提供读取服务。例如:zk,有 1~5 个节点,写入了一个最新的数据,最新数据写入到节点 1~3,会返回成功。然后读取请求过来要读取最新的节点数据,请求可能被分配到节点 4~5 。而此时最新数据还没有同步到节点4~5。会读取不到最近的数据。如果想要读取到最新的数据,可以在读取前使用 sync 命令
  • zk启动节点不能偶数台,这也是错误的。zk 是需要一半以上节点才能正常工作的。例如创建 4 个节点,半数以上正常节点数是 3。也就是最多只允许一台机器 down 掉。而 3 台节点,半数以上正常节点数是 2,也是最多允许一台机器 down 掉。4 个节点,多了一台机器的成本,但是健壮性和 3 个节点的集群一样。基于成本的考虑是不推荐的

zookeeper保证数据一致性的协议

而在我们的zookeeper中保证数据一致性用的是ZAB协议。通过这个协议来进行ZooKeeper集群间的数据同步,保证数据的一致性。

我们先来简单看一下zab协议是工作流程(图):
图片描述

ZooKeeper写数据的机制是客户端把写请求发送到leader节点上,如果发送的是follower节点,follower节点会把写请求转发到leader节点,leader节点会把数据通过proposal请求发送到所有节点,包括自己,所有到节点接受到数据以后都会写到自己到本地磁盘上面,写好了以后会发送一个ack请求给leader,leader只要接受到过半的节点发送ack响应回来,就会发送commit消息给各个节点,各个节点就会把消息放入到内存中,放内存是为了保证高性能,该消息就会用户可见了。

那这个时候,若ZooKeeper要想保证数据一致性,就需考虑如下两个情况

  • 情况一:leader执行commit了,还没来得及给follower发送commit的时候,leader宕机了,这个时候如何保证消息一致性?
  • 情况二:leader的事务性请求已经在部分节点应用了,这个时候leader挂掉了,如何保证消息一致性?

如果是Leader故障,事务型请求未提交的情况,也就是只在Leader服务器上提出但没有提交的操作的操作需要丢弃掉。因为实际上它只是完成了第一阶段。
图片描述

Leader故障,事务型请求已提交的情况,也就是已在Leader服务器上提交的操作最终被所有的服务器节点都提交。等后面讲到ZAB协议,概念就更清晰了,在某个节点提交应用成功,那这个节点的zxid肯定更大,决定它会被选举为新的leader。然后再进行同步。

图片描述

Leader选举过程

好,接下来我们来看一下Leader的选举过程,那么对于Zookeeper集群来说,当出现以下两种情况的时候,就需要进行Leader选举:

  1. 集群启动期间Leader选举
  2. 集群运行期间Leader故障

然后整个Leader选举的过程,就是一个投票的过程,然后当某一个节点,它接收到超过半数节点的选票的话,就可以认为它当选Leader了。但是这里要投给谁,其实还是有讲究的。

ZooKeeper集群选举里面,当一个节点要决定这个选票要投给谁的时候,会根据两个重要的ID来进行判断:

  1. 首先是看事务ID(zxid),哪个服务器上的事务ID最大,就投给哪个。【对应这个场景:Leader故障,事务型请求已提交
  2. 那么当两个节点事务ID一样的话,就会看节点编号,也就是myid里面存的编号,哪个大投给哪个

而在ZooKeeper里面,有个轮次的概念,就是每一次投票它其实是有时间限制的syncLimit:集群中的follower服务器与leader服务器之间请求和应答之间能容忍的最多心跳数(tickTime的数量)。,如果当前投出去的票,没有收到反馈,那么在等待一段时间后,就当作超时处理,这个时候就可以发起一下轮的投票,诶但是有可能,上一轮的反馈它是因为网络延迟的问题,这个时候才收到,那么因为现在已经是开始了下一轮了,所以这个也只能当做无效选票处理了。

选举算法

好 有了这些选举的标准和规则之后,就可以来开始选举了,选举算法:

  1. 首先每个节点均发起选举自己为领导者的投票(自己的投给自己);因为刚开始也不知道哪个节点的事务ID最大,所以就先给自己一票再说,接着就给其它节点发消息来拉票,拉票的时候,像在选举的时候一样,拉票的时候就会说自己能力多牛,那么这里也一样,它会把当前节点上最大的事务ID给带过去
  2. 其它节点收到拉票请求时,就会比较发起者的事务ID,是否比自己最新的事务ID大,如果比自己的ID大,那好就给它投一票吧,否则就不投给它了。那如果事务ID相等的话,则比较发起者的服务器编号。这一点呢我们前面也说到过,就像选举的时候,先看一下能力谁强,再看一下谁的年纪大。
  3. 然后经过每一轮的投票之后,就看有没有哪个节点,它获得的票数(含自己的)大于集群的半数,有的话就说明这个节点竞选成功,成为了Leader节点,未超过半数且领导者未选出,则再次发起投票。

集群启动期间Leader选举

图片描述

假设一个 Zookeeper 集群中有5台服务器,id从1到5编号,并且它们都是尚未启动的,没有历史数据。
图片描述

服务器1启动

服务器1启动,发起一次选举,服务器1投自己一票,此时服务器1票数一票,不够半数以上,要大于等于3票,选举无法完成。 所以,这个时候对投票结果:服务器1为1票。

服务器1状态保持为LOOKING。

服务器2启动

服务器2启动,也发起一次选举,服务器1和2分别投自己一票,此时服务器1发现服务器2的服务id比自己大,更改选票投给服务器2。 所以投票结果:服务器1为0票,服务器2为2票。

服务器1,2状态保持LOOKING
图片描述

服务器3启动

服务器3也启动了,同时也发起一次选举,服务器1、2、3先投自己一票,然后因为服务器3的服务器id最大,两者更改选票投给为服务器3;

这时候,投票结果为:服务器1为0票,服务器2为0票,服务器3为3票。此时服务器3的票数已经超过半数,服务器3当选Leader。

服务器1,2更改状态为FOLLOWING,服务器3更改状态为LEADING

图片描述

服务器4启动

服务器4启动,也加入进来,发起一次选举,此时服务器1,2,3已经不是LOOKING 状态,不会更改选票信息。交换选票信息结果:服务器3为3票,服务器4为1票。此时服务器4服从多数,更改选票信息为服务器3。

服务器4并更改状态为FOLLOWING。

服务器5启动

与服务器4一样投票给3,此时服务器3一共5票,服务器5为0票。

服务器5并更改状态为FOLLOWING。

最终的结果:

服务器3是 Leader,状态为 LEADING;其余服务器是 Follower,状态为 FOLLOWING。

好,这就是服务器初始化启动时候的选举过程。

服务器运行期间 Leader 故障

那么,我们继续来看一下第二种选举过程,服务器运行期间 Leader 故障。

我们来看一下这个场景:

在 Zookeeper运行期间 Leader 和 follower 各司其职,当有follower 服务器宕机或加入不会影响 Leader,但是一旦 Leader 服务器挂了,那么整个 Zookeeper 集群将暂停对外服务,会触发新一轮的选举。

运行期选举

运行期选举与初始状态投票过程基本类似,大致可以分为以下几个步骤:

  • 状态变更。Leader 故障后,剩下的follower节点因为跟Leader联系不上了,这个时候它会将自己的服务器状态变更为LOOKING,然后开始进入Leader选举过程。
  • 每个Server会发出投票。
  • 接收来自各个服务器的投票,如果其它服务器的数据比自己的新会改投票。
  • 处理和统计投票,每一轮投票结束后都会统计投票,超过半数即可当选。
  • 改变服务器的状态,宣布当选

初始状态下服务器3当选为Leader,假设现在服务器3故障宕机了,此时每个服务器上zxid可能都不一样,server1为10,server2为12,server4为10,server5为10

好,这里我们以这张图来说明比较好容易理解点:

图片描述

1)第一次投票,每台机器都会将票投给自己。

2)接着每台机器都会将自己的投票发给其它机器,如果发现其它机器的zxid比自己大,那么就需要改投票重新投一次。比如server1 收到了三张票,发现server2的xzid为102,pk一下发现自己输了,后面果断改投票选server2为老大,同理,因为从图中很明显看到服务器2点xzxid是最大的,所以最后是服务器2当选leader服务器。

简单来将,通常哪台机器服务器上的数据越新,那么越有可能成为leader,原因也很简单,数据越新,那么它的zxid也就越大,也就越能够保证数据的恢复。如果集群中有几个相同的最大的zxid,那么服务器id较大的服务器成为leader。

同步的过程

在选出 Leader 之后,zk 就进入状态同步的过程。其实就是把最新的 zxid 对应的日志数据,应用到其他的节点中。此 zxid 包含 follower 中写入日志但是未提交的 zxid 。称之为服务器提议缓存队列 committedLog 中的 zxid。

同步会完成三个 zxid 值的初始化。

peerLastZxid:该 learner 服务器最后处理的 zxid。 minCommittedLog:leader服务器提议缓存队列 committedLog 中的最小 zxid。 maxCommittedLog:leader服务器提议缓存队列 committedLog 中的最大 zxid。 系统会根据 learner 的peerLastZxid和 leader 的minCommittedLogmaxCommittedLog做出比较后做出不同的同步策略

直接差异化同步

场景:peerLastZxid介于minCommittedLogZxidmaxCommittedLogZxid

此种场景出现在,上文提到过的,Leader 发出了同步请求,但是还没有 commit 就 down 了。 leader 会发送 Proposal 数据包,以及 commit 指令数据包。新选出的 leader 继续完成上一任 leader 未完成的工作。

例如此刻Leader提议的缓存队列为 0x20001,0x20002,0x20003,0x20004,此处learn的peerLastZxid为0x20002,Leader会将0x20003和0x20004两个提议同步给learner

先回滚在差异化同步/仅回滚同步

此种场景出现在,上文提到过的,Leader写入本地事务日志后,还没发出同步请求,就down了,然后在同步日志的时候作为learner出现。

例如即将要 down 掉的 leader 节点 1,已经处理了 0x20001,0x20002,在处理 0x20003 时还没发出提议就 down 了。后来节点 2 当选为新 leader,同步数据的时候,节点 1 又神奇复活。如果新 leader 还没有处理新事务,新 leader 的队列为,0x20001, 0x20002,那么仅让节点 1 回滚到 0x20002 节点处,0x20003 日志废弃,称之为仅回滚同步。如果新 leader 已经处理 0x30001 , 0x30002 事务,那么新 leader 此处队列为0x20001,0x20002,0x30001,0x30002,那么让节点 1 先回滚,到 0x20002 处,再差异化同步0x30001,0x30002。

全量同步

peerLastZxid小于minCommittedLogZxid或者leader上面没有缓存队列。leader直接使用SNAP命令进行全量同步

欢迎大家学习!
主页:大能老师
实战课:2022全新版!Java分布式架构设计与开发实战

打开App,阅读手记
4人推荐
发表评论
随时随地看视频慕课网APP

热门评论

总结太全面了,全部协议都有讲到,厉害👍🏻

查看全部评论