关于Zookeeper的ZAB一致性协议

128 阅读8分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 2 天,点击查看活动详情

Zookeeper 为了保证高可用通常我们在部署的时候会以分布式集群的方式进行部署,但是 Zookeeper 在这种分布式的环境下会由于网络故障造成各种问题,比如各个节点之间数据不一致等,但是 Zookeeper 作为一个协调服务的中间件出现这种由于网络故障带来的问题是非常严重

本篇文章就来讲讲 Zookeeper 集群会造成哪些问题以及 Zookeeper 是如何进行解决的

Zookeeper在集群下所产生的问题

集群主节点宕机

Zookeeper 的集群模式为 Master/Slave (主从)模式,在部署的时候通常是一主多从,但是在 Zookeeper 集群概念中 Zookeeper 的主节点被称为 Leader 节点,从节点称为 Follwer 或 Observer 节点,Leader节点对外提供中读写服务,Follwer 或 Observer 只提供读服务

如下所示:

image.png

整个集群中都依靠 Leader 节点对外提供服务,那么当 Leader 节点宕机了,整个集群就无法对外提供服务了

此时需要人工去解决 Leader 节点宕机问题,这好像看起来没啥问题,但是如果这个故障是发生在凌晨,此时维护系统的相关人员都睡觉了,那么这个故障得需要拖到相关人员醒来才可以解决

从凌晨宕机到相关人员醒来这个时间段内,整个 Zookeeper 集群是无法对外提供服务的,从而导致整个业务系统出现故障及性能急速下降

因此完全依靠人工来解决 Leader 节点宕机问题是不太靠谱的

集群脑裂问题

既然人工不太靠谱,于是 Zookeeper 就自己实现了一套宕机自动恢复机制,这个机制就是选举机制

当 Leader 节点宕机之后,其他的 Follwer 节点检测到 Leader 节点宕机之后 (这里Observer 节点不参与选举),这些 Follwer 或 Observer 节点内部会发起一个选举机制,来从这些节点里选举出一个 Follwer 节点来做 Leader 节点

所谓集群的脑裂就是集群中产生了两个 Leader 节点,而正是由于这种选举机制才会造成集群的脑裂问题

集群的在选举的过程中会由于网络问题,产生两个 Leader 节点

具体产生的过程如下:

1、Leader 节点由于网络问题导致 Follwer 与 Leader 之间通讯不了了 ( Follwer 节点通过心跳机制来检测 Leader 节点是否发生故障)

2、Follwer 节点以为 Leader 节点宕机了,于是内部发起选举,选举出一个新的 Leader

3、以前老的 Leader 节点网络突然又恢复了,从而导致整个集群出现了两个 Leader 节点

两个主节点同时对外提供服务,这就会导致集群中数据的不一致

Leader 节点与 Follwer 节点数据不一致

除了集群脑裂会造成 Leader 节点与 Follwer 节点数据数据不一致外,Leader 节点与 Follwer 节点通常也会因为网络或 Follwer 节点故障问题造成 Leader 节点与 Follwer 节点数据同步失败造成数据不一致

ZAB一致性协议

ZAB 全称为 Zookeeper Atomic Broadcast ,译为 Zookeeper 原子广播协议,是 Zookeeper 官方专门为了解决 Zookeeper 在分布式环境下造成的数据不一致问题而设计的一种协议,我们也可以称为算法

当然 ZAB 除了解决数据一致性问题之外,另外还支持崩溃自动恢复

以下为官方说法:

ZAB 协议是为分布式协调服务 Zookeeper 专门设计的一种支持崩溃恢复的原子广播协议

崩溃恢复

上面我们说的 Leader 选举机制就是 Zookeeper 崩溃恢复的具体实现,它属于 ZAB 协议的范畴

那么 Zookeeper 是如何通过 Leader 选举来达到崩溃恢复的目的的呢,下面我具体来讲下

我们先来了解下在选举机制过程中的几种概念:

  • 选票:各个 Follwer 节点在开始选举的时候,会向其他的 Follwer 节点发送选票信息,选票信息包括了它自己认为可以当选为 Leader 节点的 Follwer 节点信息,选票的信息是由 myid、zxid、peerEpoch 组成

  • myid: 每台 Zookeeper 服务器都需要在各自的数据文件夹下创建一个 myid 文件,这个 myid 就是 Zookeeper 在集群中的唯一标识

  • zxid:为 Zookeeper 集群数据的最大事务ID,用于标识一次更新操作的 ProposalID,为了保证顺序性,该 zxid 必须单调递增

  • peerEpoch:选举周期,表示当前服务器发起的第多少轮投票

  • 服务器状态:

    • LOOKING :该状态下的服务器认为集群中没有 Leader ,会发起 Leader 选举
    • FOLLOWING:跟随者状态。表明当前服务器角色是Follower,并且它知道Leader是谁
    • LEADING :表明当前服务器角色是Leader,它会维护与Follower间的心跳
    • OBSERVING :表明当前服务器角色是Observer,与Folower唯一的不同在于不参与选举,也不参与集群写操作时的投票。

了解完这些概念之后,再来看看是如何进行选举的

我们假设有 Z1 、Z2 、Z3、Z4 四台 Zookeeper 服务器,这四台 Zookeeper 的 myid 为 1、2、3、4,其中 Z1 为 Leader 节点,其他的为 Follwer 节点

其中 Z1 的状态为 LEADING ,其他的服务器状态为 FOLLOWING

对应集群架构图如下所示:

image.png

他们之间通过心跳机制保持连接,当 Leader 节点宕机后,其他的 Follwer 节点将自己的服务器状态改为 LOOKING 并进入到选举阶段

刚开始各自的 Follwer 节点先会投自己一票,此时各节点的投票箱里只有自己的那一票,假设这里Z2、Z3、Z4 各自的选票为(2,2,1)、(3,5,1),(4,9,1)

如图所示:

image.png

再将自己的投票发给其他节点,于是各自节点的投票箱里就有另外两个节点的选票

image.png

各个 Follwer 节点拿到所有的选票之后,再进行选票 PK ,PK的规则如下:

1、优先检查各个节点选票里的 peerEpoch ,peerEpoch 比较大的服务器优先作为 Leader

2、如果 peerEpoch 是一样的,那么再比较 ZXID ,ZXID 较大的作为 Leader

3、如果 ZXID 是一样的,那么再比较 myid,myid 较大的作为 Leader

PK 完之后,再判断投票箱里的投票数是否过半,如果过半,则将过半选票对应的节点 正式选举为 Leader 节点

这里按照上面的PK规则,我们得出的结果如下:

1、Z2 收到 Z3 和 Z4 的选票 ,并将 Z3、Z4 的投票也放进投票箱,此时 Z2 的投票箱里有 Z2、Z3、Z4 的选票,Z2 通过 PK 规则,将自己的选票更新为 (4,9,1),并将更新的选票又发出去

2、同理,Z3 收到 Z2 和 Z4 的选票,此时 Z3 的投票箱里有 Z2、Z3、Z4 的选票,Z3 通过 PK 规则将自己的选票更新为 (4,9,1) ,并将更新的选票发出去

3、Z4 同上,也是投票箱里有 Z2、Z3、Z4 的选票 ,通过 PK 规则将自己的选票更新为 (4,9,1) ,并将更新的选票发出去

4、各个节点分别收到选票 (4,9,1),此时各个投票箱里的选票为:(2,2,1)、(3,5,1)、(4,9,1)、(4,9,1)

此时 (4,9,1) 的投票数为 2 ,其他的选票都是 1 ,于是就把选票为 (4,9,1) 的节点,也就是 Z4 节点选举为 Leader 节点,其他的节点为 Follwer 节点,如图所示:

image.png

消息广播

Leader 节点在跟 Follwer 节点同步数据的时候,往往会因为 Follwer 节点故障或网络问题导致数据同步失败,从而导致整个集群中各个节点数据不一致

为了解决这种数据不一致问题,ZAB 协议提供了一套消息广播机制,这种机制也称为原子广播

为啥叫原子广播呢

这是因为 Leader 在同步数据的时候,会为这一次同步数据的操作创建一个事务 Proposal ,然后将这个事务广播给其他的 Follwer 节点,然后当 Leader 收到 Follwer 节点过半数反馈的时候才会将这个事务提交,有点类似于二阶段提交

我们再来看看具体是如何来完成整个事务提交的

在上面的例子中,集群通过选举选,选举出 Z4 为 Leader 节点,Z4 接收一个客户端的请求的时候,会将其生成为对应的事务 Proposal,并将其发给其他的 Follwer 节点,如下:

image.png

其他的 Follwer 节点在接收到这个 Proposal 之后,会将这个事务写入到这个事务文件中,不会做 commit 操作,此时客户端是看不到 Leader 同步给 Follwer 节点 的数据

事务文件写完之后,会给 Leader 响应一个 ACK ,如下:

image.png

当收到这个 ack 过半数的时候,会再一次向 Follwer 节点发送 commit 操作提交事务

例如,这里 Follwer 节点数量为 2,当收到的 ack 数量为 1 或 超过1 的时候就会发送 commit 操作

当各个 Folllwer 节点收到这个 commit 操作之后,会提交自身的事务,同时 Leader 节点也会提交事务,如下:

image.png

到这里整个原子广播基本上就完成了

结束语

码字不易,还希望多多点赞、收藏,支持一下