一、前言
想起很久以前在某个客户现场,微服务 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 模型其实可以理解为类文件系统,如下图:
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) &