Zookeeper ZAB 一致性协议

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
注册配置 MSE Nacos/ZooKeeper,118元/月
容器服务 Serverless 版 ACK Serverless,952元额度 多规格
简介: Zookeeper 是通过 ZAB 一致性协议来实现分布式事务的最终一致性。

ZAB 协议介绍


ZAB 全称为 Zookeeper Atomic Broadcast(Zookeeper  原子广播协议)


ZAB 协议是为分布式协调服务ZooKeeper专门设计的一种支持崩溃恢复的一致性协议。基于该协议,ZooKeeper 实现了一种主从模式的系统架构来保持集群中各个副本之间的数据一致性。


ZAB的消息广播过程使用的是原子广播协议,类似于二阶段提交。针对客户端的请求,Leader服务器生成对应的事务提议,并将其发送给集群中所有的 Follower 服务器。然后收集各自的选票,最后进行事务提交。如图:


image.png


在 ZAB 协议中的二阶段提交,移除了中断逻辑。所有的 Follower 服务器要么正常反馈 Leader 提出的事务提议,要么就抛弃 Leader 服务器。同时,我们可以在过半的 Follower 服务器已经反馈 ACK 后,就开始提交事务提议了。


Leader 服务器会为事务提议分配一个全局单调递增的 ID,称为事务 ID(ZXID)。由于 ZAB 协议需要保证每一个消息严格的因果关系,因此需要将每一个事务提议按照其 ZXID 的先后顺序进行处理。


在消息广播过程中,Leader 服务器会为每一个 Follower 服务器分配一个队列,然后将事务提议依次放入到这些队列中去,并且根据 FIFO 的策略进行消息发送。


每一个 Follower 服务器接收到这个事务提议后,会把该事务提议以事务日志的形式写入到本地磁盘中,并且写入成功后,反馈给 Leader 服务器 ACK。


当 Leader 服务器收到过半 Follower 服务器的 ACK,就发送一个 COMMIT 消息,同时 Leader 自身完成事务提交,Follower 服务器接收到 COMMIT 消息后,也进行事务提交。


之所以采用原子广播协议协议,是为了保证分布式数据一致性。过半的节点数据保存一致性。


消息广播


你可以认为消息广播机制是简化版的 2PC协议,就是通过如下的机制保证事务的顺序一致性的。


image.png


客户端提交事务请求时 Leader 节点为每一个请求生成一个事务 Proposal,将其发送给集群中所有的 Follower 节点,收到过半 Follower的反馈后开始对事务进行提交,ZAB 协议使用了原子广播协议;在 ZAB 协议中只需要得到过半的 Follower 节点反馈 Ack 就可以对事务进行提交,这也导致了 Leader 节点崩溃后可能会出现数据不一致的情况,ZAB 使用了崩溃恢复来处理数字不一致问题;消息广播使用了TCP 协议进行通讯所有保证了接受和发送事务的顺序性。广播消息时 Leader 节点为每个事务 Proposal分配一个全局递增的 ZXID(事务ID),每个事务 Proposal 都按照 ZXID 顺序来处理;


Leader 节点为每一个 Follower 节点分配一个队列按事务 ZXID 顺序放入到队列中,且根据队列的规则 FIFO 来进行事务的发送。Follower节点收到事务 Proposal 后会将该事务以事务日志方式写入到本地磁盘中,成功后反馈 Ack 消息给 Leader 节点,Leader 在接收到过半Follower 节点的 Ack 反馈后就会进行事务的提交,以此同时向所有的 Follower 节点广播 Commit 消息,Follower 节点收到 Commit 后开始对事务进行提交;


崩溃恢复


消息广播过程中,Leader 崩溃了还能保证数据一致吗?当 Leader 崩溃会进入崩溃恢复模式。其实主要是对如下两种情况的处理。


  1. Leader 在复制数据给所有 Follwer 之后崩溃,怎么处理?


  1. Leader 在收到 Ack 并提交了自己,同时发送了部分 commit 出去之后崩溃,怎么处理?


针对此问题,ZAB 定义了 2 个原则:


  1. ZAB 协议确保执行那些已经在 Leader 提交的事务最终会被所有服务器提交。


  1. ZAB 协议确保丢弃那些只在 Leader 提出/复制,但没有提交的事务。


至于如何实现确保提交已经被 Leader 提交的事务,同时丢弃已经被跳过的事务呢?核心是通过 ZXID 来进行处理。在崩溃过后进行恢复的时候会选择最大的 zxid 作为恢复的快照。 这样的好处是:  可以省略事务提交的检查和事务的丢弃工作以提升效率


数据同步


完成Leader选举之后,在正式开始工作之前,Leader服务器会去确认事务日志中所有事务提议(指已经提交的事务提议)是否都已经被过半的机器提交了,即是否完成数据同步。下面是ZAB协议的 数据同步过程。


Leader服务器为每一个Follower服务器准备一个队列,将那些没有被Follower服务器同步的事务以事务提议的形式逐个发送给Follower服务器,并在每一个事务提议消息后面发送一个commit消息,表示该事务已被提交。


等到Follower服务器将所有其未同步的事务提议都从Leader服务器上面同步过来,并且应用到本地数据库后,Leader服务器就会将该Follower服务器加入到真正可用的Follower列表中。


ZXID 的设计


ZXID 是一个64位的数字,  如下图所示。


image.png


其中低 32 位是一个简单的单调递增的计数器,Leader 服务器产生一个新的事务提议的时候,都会对该计数器 +1。


高 32 位,用来区分不同的 Leader 服务器。具体做法是,每选举产生一个新的 Leader 服务器,就会从 Leader 服务器的本地日志中取出一个最大的 ZXID,生成对应的 epoch 值,然后再进行加1操作,之后就会以该值作为新的 epoch。并将低 32 位从 0 开始生成 ZXID。(我理解这里的 epoch 代表的就是一个 Leader 服务器的标志,每次选举 Leader 服务器,那么 epoch 值就会更新,代表是这段时期由这个新的 Leader 服务器进行事务请求的处理)。


ZAB 协议中通过 epoch 编号来区分 Leader 周期变化,能够有效避免不同 Leader 服务器使用相同的 ZXID。


下面是我 Leader 节点的 zxid 生成核心代码大家可以看一下。


// Leader.java
void lead() throws IOException, InterruptedException {
  // ....
  long epoch = getEpochToPropose(self.getId(), self.getAcceptedEpoch());
  zk.setZxid(ZxidUtils.makeZxid(epoch, 0));
  // ....
}
//
public long getEpochToPropose(long sid, long lastAcceptedEpoch) throws InterruptedException, IOException {
  synchronized (connectingFollowers) {
    // ....
    if (isParticipant(sid)) {
      // 将自己加入连接队伍中,方便后面判断 lead 是否有效
      connectingFollowers.add(sid);
    }
    QuorumVerifier verifier = self.getQuorumVerifier();
    // 如果有足够多的 follower 进入, 选举有效,则无需等待,并通过其他等待的线程,类似 Barrier
    if (connectingFollowers.contains(self.getId()) && verifier.containsQuorum(connectingFollowers)) {
      waitingForNewEpoch = false;
      self.setAcceptedEpoch(epoch);
      connectingFollowers.notifyAll();
    } else {
      // ....
      // followers 不够就进入等待, 超时时间为 initLimit
      while (waitingForNewEpoch && cur < end && !quitWaitForEpoch) {
        connectingFollowers.wait(end - cur);
        cur = Time.currentElapsedTime();
      }
      // 超时退出,重新选举
      if (waitingForNewEpoch) {
        throw new InterruptedException("Timeout while waiting for epoch from quorum");
      }
    }
    return epoch;
  }
}
// ZxidUtils
public static long makeZxid(long epoch, long counter) {
  return (epoch << 32L) | (counter & 0xffffffffL);
}


ZAB 协议实现


写数据的过程


image.png


参考资料





相关实践学习
基于MSE实现微服务的全链路灰度
通过本场景的实验操作,您将了解并实现在线业务的微服务全链路灰度能力。
相关文章
|
1月前
|
分布式计算 负载均衡 算法
Hadoop-31 ZooKeeper 内部原理 简述Leader选举 ZAB协议 一致性
Hadoop-31 ZooKeeper 内部原理 简述Leader选举 ZAB协议 一致性
28 1
|
2月前
|
监控
分布式-Zookeeper-Zab协议
分布式-Zookeeper-Zab协议
|
3月前
|
消息中间件 分布式计算 安全
这一次,彻底弄懂ZooKeeper协议
ZooKeeper是动物园的意思,在2012年官方来给ZooKeeper写了这么一段有趣的“ZooKeeper之道”,难怪ZooKeeper现在发展得这么好。动物园管理员对他们负责的动物和参观动物的游客都尽心尽力。他们遵循一套守则,至今只有同行才知道,这套守则可以保证动物和游客的安全。大家好,我是南哥。一个Java学习与进阶的领路人,相信对你通关面试、拿下Offer进入心心念念的公司有所帮助。
134 4
这一次,彻底弄懂ZooKeeper协议
|
4月前
|
Nacos 微服务
Zookeeper 的 ZAB 协议 以及 zookeeper 与 nacos 注册中心比对
Zookeeper 的 ZAB 协议 以及 zookeeper 与 nacos 注册中心比对
90 4
|
6月前
|
消息中间件
【ZooKeeper系列】那ZooKeeper为什么还采用ZAB协议
ZooKeeper的流程是这样的,针对客户端的事务请求,Leader服务器会为其生成对应的事务Proposal,并发送给集群中其余机器,然后再分别收集各自的选票。因为ZAB协议将二阶段提交中的事务中断逻辑移除,所以只需要收集过半Follower服务器的反馈Ack后即可,最后就是进行事务提交。
【ZooKeeper系列】那ZooKeeper为什么还采用ZAB协议
|
6月前
|
存储 负载均衡 网络协议
ZooKeeper【基础 01】简介+设计目标+核心概念+ZAB协议+典型应用场景
【4月更文挑战第10天】ZooKeeper【基础 01】简介+设计目标+核心概念+ZAB协议+典型应用场景
91 1
|
6月前
|
前端开发 JavaScript 算法
分布式系统的一致性级别划分及Zookeeper一致性级别分析
分布式系统的一致性级别划分及Zookeeper一致性级别分析
|
6月前
|
网络协议 中间件 数据库
Zookeeper学习系列【三】Zookeeper 集群架构、读写机制以及一致性原理(ZAB协议)
Zookeeper学习系列【三】Zookeeper 集群架构、读写机制以及一致性原理(ZAB协议)
259 0
|
2月前
|
安全 应用服务中间件 API
微服务分布式系统架构之zookeeper与dubbo-2
微服务分布式系统架构之zookeeper与dubbo-2
|
2月前
|
负载均衡 Java 应用服务中间件
微服务分布式系统架构之zookeeper与dubbor-1
微服务分布式系统架构之zookeeper与dubbor-1