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

分布式协调框架 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)                &