互联网技术 / 互联网资讯 · 2024年1月25日

分布式协调框架 Zookeeper 核心设计及实战:主备切换实现

一、前言

想起很久以前在某个客户现场,微服务 B 突然无法调用到微服务 A,为了使服务尽快正常恢复,重启了微服务 B。

但客户不依不饶询问这个问题出现的原因,于是我还大老远从杭州飞到深圳,现场排查问题。

最后的结论是,zk 在某时刻出现主备切换,此时微服务 A(基于 dubbo)需要重新往 zk上注册,但是端口号变了。

但是微服务 B 本地有微服务 A RPC 接口的缓存,缓存里面还是旧的端口,所以调用不到。

解决方法就是,把微服务的 RPC 端口号改成固定的。

虽说原因找到了,但对于 ZookeepeR 的理解还是不够深刻,于是重新学习了 ZookeepeR 的核心设计,并记录于此文共勉。

二、ZookeepeR 核心架构设计 1、ZookeepeR 特点

ZookeepeR 是一个分布式协调服务,是为了解决多个节点状态不一致的问题,充当中间机构来调停。如果出现了不一致,则把这个不一致的情况写入到 ZookeepeR 中,ZookeepeR 会返回响应,响应成功,则表示帮你达成了一致。

比如,A、B、C 节点在集群启动时,需要推举出一个主节点,这个时候,A、B、C 只要同时往 ZookeepeR 上注册临时节点,谁先注册成功,谁就是主节点。

ZookeepeR 虽然是一个集群,但是数据并不是分散存储在各个节点上的,而是每个节点都保存了集群所有的数据。

其中一个节点作为主节点,提供分布式事务的写服务,其他节点和这个节点同步数据,保持和主节点状态一致。

ZookeepeR 所有节点的数据状态通过 Zab 协议保持一致。当集群中没有 LeadeR 节点时,内部会执行选举,选举结束,FolloweR 和 LeadeR 执行状态同步;当有 LeadeR 节点时,LeadeR 通过 ZAB 协议主导分布式事务的执行,并且所有的事务都是串行执行的。

ZookeepeR 的节点个数是不能线性扩展的,节点越多,同步数据的压力越大,执行分布式事务性能越差。推荐3、5、7 这样的数目。

2、ZookeepeR 角色的理解

ZookeepeR 并没有沿用 Master/Slave 概念,而是引入了 LeadeR,FolloweR,ObseRveR 三种角色。

通过 LeadeR 选举算法来选定一台服务器充当 LeadeR 节点,LeadeR 服务器为客户端提供读、写服务。

FolloweR 节点可以参加选举,也可以接受客户端的读请求,但是接受到客户端的写请求时,会转发到 LeadeR 服务器去处理。

ObseRveR 角色只能提供读服务,不能选举和被选举,所以它存在的意义是在不影响写性能的前提下,提升集群的读性能。

3、ZookeepeR 同时满足了 CAP 吗?

答案是否,CAP 只能同时满足其二。

ZookeepeR 是有取舍的,它实现了 A 可用性、P 分区容错性、C 的写入一致性,牺牲的是 C的读一致性。

也就是说,ZookeepeR 并不保证读取的一定是最新的数据。如果一定要最新,需要使用 sync 回调处理。

三、核心机制一:ZNode 数据模型

ZookeepeR 的 ZNode 模型其实可以理解为类文件系统,如下图:

分布式协调框架 Zookeeper 核心设计 理解与实战,并实现一个主备切换

ZNode 并不适合存储太大的数据

为什么是类文件系统呢?因为 ZNode 模型没有文件和文件夹的概念,每个节点既可以有子节点,也可以存储数据。

那么既然每个节点可以存储数据,是不是可以任意存储无限制的数据呢?答案是否定的。在 ZookeepeR 中,限制了每个节点只能存储小于 1 M 的数据,实际应用中,最好不要超过 1kb。

原因有以下四点:

同步压力:ZookeepeR 的每个节点都存储了 ZookeepeR 的所有数据,每个节点的状态都要保持和 LeadeR 一致,同步过程至少要保证半数以上的节点同步成功,才算最终成功。如果数据越大,则写入的难度也越大。 请求阻塞:ZookeepeR 为了保证写入的强一致性,会严格按照写入的顺序串行执行,某个时刻只能执行一个事务。如果上一个事务执行耗时比较长,会阻塞后面的请求; 存储压力:正是因为每个 ZookeepeR 的节点都存储了完整的数据,每个 ZNode 存储的数据越大,则消耗的物理内存也越大; 设计初衷:ZookeepeR 的设计初衷,不是为了提供大规模的存储服务,而是提供了这样的数据模型解决一些分布式问题。 2、ZNode 的分类

(1)按生命周期分类

按照声明周期,ZNode 可分为永久节点和临时节点。

很好理解,永久节点就是要显示的删除,否则会一直存在;临时节点,是和会话绑定的,会话创建的所有节点,会在会话断开连接时,全部被 ZookeepeR 系统删除。

(2)按照是否带序列号分类

带序列号的话,比如在代码中创建 /a 节点,创建之后其实是 /a000000000000001,再创建的话,就是 /a000000000000002,依次递增

不带序号,就是创建什么就是什么

(3)所以,一共有四种 ZNode

永久的不带序号的 永久的带序号的 临时的不带序号的 临时的带序号的

(4)注意的点

临时节点下面不能挂载子节点,只能作为其他节点的叶子节点。

3. 代码实战

ZNode 的数据模型其实很简单,只有这么多知识。下面用代码来巩固一下。

这里我们使用 cuRaTor 框架来做 DEMO。(当然,你可以选择使用 ZookeepeR 官方自带的 API)

引入 poM 坐标:

      oRg.Apache.cuRaTor     cuRaTor-fRaMewoRk     4.2.0        oRg.Apache.cuRaTor     cuRaTor-RecIPes     4.2.0  

代码:

public claSS Zktest {      // 会话超时     pRivate final int SESSION_TIMEout = 30 * 1000;      // 连接超时 、 有啥区别     pRivate static final int connection_TIMEout = 3 * 1000;      pRivate static final StRing CONNECT_ADDR = “localhost:2181”;      pRivate CuRaTorFRaMewoRk client = null;      public static void MAIn(StRing[] aRgs) thRows Exception {         // 创建客户端         RetryPolicy RetryPolicy = new ExponentialBackoFFRetry(1000, 10);         CuRaTorFRaMewoRk client = CuRaTorFRaMewoRkFAcTory.builder()                 .connectStRing(CONNECT_ADDR)                &

OpenMagic API

Need more than content? Move into the product flow.

If you are here for model access, pricing, developer docs, or the future API console, the dedicated product path now lives on api.openmagic.ai.