【Java核心能力】一篇文章了解 ZooKeeper 底层运行原理

欢迎关注公众号(通过文章导读关注:【11来了】),及时收到 AI 前沿项目工具及新技术的推送!

在我后台回复 「资料」 可领取编程高频电子书
在我后台回复「面试」可领取硬核面试笔记

文章导读地址:点击查看文章导读!

感谢你的关注!

一篇文章了解 ZooKeeper 底层运行原理

该文章主要说一下 ZooKeeper 底层运行原理,以及其中比较核心的功能

CAP 是分布式系统中的基本理论,一般只能满足 AP 和 CP,常用的注册中心 ZK 和 Nacos:

  • ZooKeeper 符合 CP选择了一致性而抛弃了部分可用性 ,因为 ZK 集群中只有 Leader 节点可以写数据,如果 Leader 挂了,需要重新选举 Leader,在这期间 ZK 集群不可用,ZK 只有 Leader 可以写,所以整个写入操作是 中心化
  • Nacos 符合 AP选择了可用性并且满足最终一致性 ,并且 Nacos 的设计是去中心化的

并且由于 ZK 的监听机制,导致在节点数量较多的时候,如果出现大量监听事件的触发,会导致 ZK 瞬时流量过大 ,因此,现在将 Zookeeper 作为服务注册中心的公司在减少,而是转向使用 Nacos 或自研注册中心

首先说一下 ZooKeeper 用来做什么

ZooKeeper 是一个分布式协调服务,提供了一个中心化的服务,用于管理和协调分布式应用中的各种配置、信息和事件

那么一般使用 ZooKeeper 中,较常用的场景就是 注册中心元数据管理

元数据管理 指对多个分布式服务中都需要用到的基础类型的数据进行管理,使用 ZK 管理的好处就是不需要在每一个分布式服务中都进行配置,只要在 ZK 中进行配置即可,并且配置变更之后,所有的分布式服务可以及时收到 数据变更的通知

注册中心 指在那些需要进行 RPC 远程调用时,将 RPC 服务注册到 ZK 中去,之后需要调用的话,可以在 ZK 中查询提供了哪些服务可以进行调用,并且可以进行 负载均衡 处理

如下图,如果提供了一个 HelloServiceImpl 的 RPC 服务,将该服务注册在 ZK 中,节点值为该服务的 IP 地址,这样调用方在需要使用服务的时候,可以拿到该服务的 IP 地址,与该服务建立 TCP 连接即可进行通信(Dubbo 的注册中心可以使用 ZK 也可以使用 Nacos)

image-20240328122134775

ZooKeeper 中的核心内容

ZooKeeper 中的核心内容如下:

  • 节点类型
  • watcher 监听机制
  • 分布式锁
  • 集群模式

接下来主要从这几块内容来介绍 ZooKeeper 的运行原理

节点类型

ZK 中的节点类型分为:持久节点、临时节点、顺序节点

持久节点:创建之后一直存在

临时节点:只要客户端断开连接,节点就会被删除

顺序节点:创建节点时,会添加全局递增的序号,经典应用场景是 分布式锁 (顺序节点既可以是持久节点也可以是临时节点)

节点使用应用场景: 在 RPC 调用中,将提供的 RPC 服务作为临时节点注册在 ZK 中,当 RPC 服务提供者与 ZK 断开连接之后,该服务在 ZK 中注册的临时节点就会被删除,那么我们客户端就可以对该节点添加监听器,当发现节点被删除之后,就可以感知到对应的服务下线了(监听器的内容会在后边说道)

ZooKeeper 的监听机制

ZooKeeper 提供了 watcher 监听机制,这是 ZK 中最常用的功能,因为多个分布式节点之间及时感知到 ZK 数据的变化就是通过 监听机制实现的

ZK 中的 watcher 有以下几个特点:

  • 一次性: watcher 被触发之后,ZK会将其从客户端的 WatchManager 中删除,也会从服务端删除,重新注册 watcher 才可以继续下一次的监听
  • 串行性: 同一 Node 的相同事件类型引发的 watcher 回调方法串行执行(也就是只有执行完w atcher 的回调,才可以重新生成 watcher 对象进行监听,如果回调执行时间太长,可能会导致监听事件的丢失,因此 ZK 也不适合在 watcher 回调中执行耗时的IO操作)

上边说的 watcher 监听是 ZK 中原生的监听机制,我们使用 ZK 都是使用它的客户端工具 Curator,该客户端工具中也封装了一些丰富的功能来提供给我们进行使用,如 分布式锁节点监听机制

这里说一下 Curator 提供的节点监听机制 ,提供了有 3 种类型的监听:

  • NodeCache :监听指定节点(单个节点监听)
  • PathChilrenCache :监听指定节点的子节点
  • TreeCache :监听指定节点

那么对于 监听机制的使用 ,这里还是以 RPC 远程服务调用为例,如果每一个 RPC 服务提供者都会将自己的 IP 地址注册到对应的节点下,那么客户端只需要对 com.zqy.service.impl.HelloServiceImpl 这个节点下的子节点进行监听,就可以感知到哪些服务已经下线,就不再对该服务进行远程调用即可,那么这里就可以使用 PathChildrenCache 来进行监听(具体的代码示例这里就不写了,只讲原理)

image-20240328122134775

分布式锁

ZK 作为分布式协调框架,用来作为分布式锁使用从他的设计目的上来讲是比较合适的,那么常用的分布式锁还有 Redis 的分布式锁(Redisson 客户端框架提供)

  • 对于这两种锁到底要如何去选择使用呢?

其实使用哪一种都可以,两者提供的分布式锁功能都可以满足日常的使用,并且使用 Redis 集群和使用 ZK 集群都可以保证较好的锁的可用性

但是两者在 极端情况下也会出现问题 ,如在 Redis 集群加锁,写入主节点之后,如果锁的信息未来得及同步到从节点,此时主节点宕机,就会导致这个锁的信息丢失,会出现 重复加锁 的情况;在 ZK 集群中,如果客户端长时间 GC 导致无法与 ZK 维持心跳,ZK 会误认为该客户端下线,将该客户端加锁的临时节点删除,也会出现 重复加锁 的情况

那么可以看到在 功能性可用性 上,两者其实差别都不大,具体选用的话,可以 根据当前项目使用的技术栈 来进行选择,比如当前项目中并没有引入 Redis 依赖,只有 ZK 依赖,那么直接使用 ZK 的分布式锁,完全没有必要去引入 Redis 集群来使用 Redis 的分布式锁,因为多一种技术,就多一份故障的风险,单单为了使用 Redis 分布式锁就去引入 Redis 集群显然小题大做了

  • ZK 中的分布式锁实现

ZK 的分布式锁就是依靠 临时顺序节点 实现的,比如说创建一把名为 lock 的锁,那么所有需要加锁的客户端会到该节点下创建 临时顺序节点 ,只有第一个创建成功的客户端可以拿到锁,其他客户端创建的临时顺序节点在后边排序,并且对前一个节点进行 监听 ,当监听到锁释放,自己就拿到了锁(可以了解一下 Curator 对读写锁出现的羊群效应的解决)

image-20240328132722256

ZooKeeper 集群

接下来说一下 ZK 的集群部署,ZK 集群中通过分布式一致性协议 ZAB 来保证数据同步的一致性,以及 Leader 选举、集群的崩溃恢复,接下来会主要 围绕 ZAB 协议 来说一下 ZK 集群相关的核心内容:

ZK 集群中有三种角色:Leader、Follower、Observer

  • Leader :执行写操作,并且向 Follower 进行同步
  • Follower :从 Leader 同步数据,执行读操作,不可以执行写操作
  • Observer :只可以执行读操作

ZK 集群其实是适合 写少读多 场景的,因为整个集群只有 1 个 Leader 可以写,其他节点只可以读,那么你可能有疑问,Follower 节点用来读数据,为什么还需要 Observer 节点呢?

这是因为在集群中 Leader 完成写请求是需要经过半数以上的 Follower 都 Ack 之后,才可以成功写入的,如果集群中 Follower 过多,会大大增加 Leader 节点等待 Follower 节点发送 Ack 的时间,导致 ZK 集群性能很差,因此 ZK 集群部署都是 小集群部署 ,一般都是 3 台或者 5 台机器

Observer 节点Observer 是只读的、不参与 Leader 选举、也不参与 ZAB 协议同步时过半 Ack 的环节,只是单纯的接收数据,同步数据,达到数据顺序一致性的效果,当读并发请求过高时,可以 通过不断添加 Observer 节点来分散读请求的压力

  • ZK 的分布式一致性协议 ZAB 保证数据同步的一致性

ZAB 协议中是采用 2PC 两阶段提交思想完成数据写入的:

采用 2PC 两阶段提交思想 的 ZAB 消息广播流程:

每一个消息广播的时候,都是基于 2PC 的思想,先是发起事务提议 Proposal 的广播,各个 Follower 返回 Ack,当过半的 Follower 都返回 Ack 之后,Leader 就发送 Commit 消息到 Follower,让大家提交事务

这里的两阶段指的就是发送 ProposalCommit

发起一个事务 Proposal 之前,Leader 会分配一个全局唯一递增的事务 id(zxid),以此来严格保证顺序

Leader 会为每个 Follower 创建一个队列,里边存放要发给 Follower 的事务 Proposal,保证了一个同步的顺序性

Follower 收到事务 Proposal 之后,就立即写入本地磁盘日志中,写入成功后数据就不会丢失了,之后返回 Ack 给 Leader,当过半的 Follower 都返回 Ack,Leader 推送 Commit 消息给全部 Follower,让大家进行事务提交,事务提交之后,数据就被写入到了 znode(也就是内存中) ,此时数据就可以被用户感知到了

1704556482793

  • ZK 的 ZAB 保证集群的崩溃恢复

下边将会介绍 ZK 集群 启动 再到 崩溃 再到 恢复 整体的流程:

ZK 集启动的时候,进入 恢复模式 ,选举一个 Leader 出来,然后 Leader 等待集群中过半的 Follower 跟他进行数据同步,只要过半的 Follower 完成数据同步,接着就退出恢复模式,可以对外提供服务了

此时,还没完成同步的 Follower 会自己去跟 Leader 进行数据同步的

之后会进入 消息广播模式 ,只有 Leader 可以接受写请求,但是客户端可以任意连接 Leader 或者 Follower,如果客户端连接到 Follower,Follower 就会将写请求转发给 Leader

Leader 收到写请求,就把请求同步给所有的 Follower,当超过半数的 Follower 都返回了 Ack,之后 Leader 先将数据写到自己的 znode 中,再给所有的 Follower 发一个 Commit 消息,让大家提交这个请求事务,Follower 收到 Commit 消息后,就会将磁盘中刚刚写入的数据往内存中的 znode 中写,之后客户端就可以读取到数据了

如果 Leader 宕机了,就会进入 恢复模式 ,重新选举一个 Leader,只要获得了过半的机器的投票,就可以成为 Leader

ZK 集群中可以容忍不超过一半的机器宕机,就比如说一个集群有 3 台机器,那么最多允许 1 台机器宕机,剩下的 2 台选举 Leader,只要 2 台机器都认可其中一台机器当 Leader,也就是超过了集群一半的机器都认可,那么就可以选举这台机器作为 Leader

新的 Leader 等待过半的 Follower 跟他同步,之后重新进入 消息广播模式

以上就是 ZK 集群恢复崩溃的整个流程了,当然我也画了一个流程图,更方便观看,如下:

1704556288655

主要就是分为 3 个阶段:

  • 集群启动时:恢复模式,Leader 选举 + 数据同步

  • 消息写入时:消息广播模式,Leader 采用 2PC 的过半写机制,给 Follower 进行同步

  • 崩溃恢复:恢复模式,Leader/Follower 宕机,只要剩余机器超过一半,就可以选举新的 Leader

  • ZK 集群的性能瓶颈在哪里呢?

瓶颈在于 Leader 的 写性能,如果 ZK 集群挂掉的话,那么很有可能就是 Leader 的写入压力过大,这对一个公司的技术平台打击是巨大的,因为像 kafka 之类的技术都是强依赖 ZK 的,Dubbo + ZK 去做服务框架的话,当服务实例达到上万甚至几十万时,大量服务的上线、注册、心跳的压力达到了每秒几万甚至十万,单个 Leader 抗几万的请求还行,十几万的话 ZK 单个 Leader 是扛不住这么多的写请求的

想要提升 Leader 的 写性能 ,目前来说也就是提升部署 ZK 的机器性能了,还有一种方式也就是将 dataLogDir 目录挂载的机器上配置 SSD 固态硬盘,以此来提升事务日志 写速度 来提升写性能!

  • ZK 集群的 Leader 选举

在 Leader 选举时,需要用到以下几个属性:

SID :服务器 ID,和 myid 一致,作为服务器的标识,不可以重复

ZXID :事务 ID,用来标识一次服务状态的变更,ZXID 全局唯一并且递增,有 64 位,高 32 位时 Leader 的 Epoch,低 32 位是递增计数器

Epoch :每个 Leader 任期的代号(没有 Leader 时,同一轮投票过程中的 Epoch 时相同的,每投完一次票,就会加 1)

当一台机器进入 Leader 选举时,当前集群可能处于以下两种状态:

  • 集群中已经有一个 Leader

如果已经有 Leader 的情况,会被告知当前服务器的 Leader 信息,之后进行状态同步即可

  • 集群中确实不存在 Leader

假设 zookeeper 有 5 台服务器,SID 分别为 1、2、3、4、5,ZXID 分别为 8、8、8、7、7,并且 SID = 3 的服务器是 Leader

如果某一时刻服务器 3 和 5 出现故障,之后服务器 1、2、4 开始 Leader 选举,规则如下:

1、EPOCH 大的选举为 Leader

2、EPOCH 相同,事务 ID 大的选举为 Leader

3、事务 ID 相同,SID 大的选举为 Leader

那么对于服务器 1、2、4 来说,最终服务器 2 选举为 Leader, 这样的选举策略可以保证有 最新数据的节点 可以竞选

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/497208.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Mysql数据库——主从复制与读写分离

目录 前言 一、主从复制 1.主从复制的定义 2.Mysql主从复制支持的类型 3.主从复制的过程 4. 主从复制出现的问题 5.解决方法 二、读写分离 1.读写分离的定义 2.读写分离的作用 3.读写分离作用场景 3.1基于程序代码内部实现 3.2基于中间代理层实现 4.主从复制与读…

Redis命令介绍

一、redis启动: 本地启动:redis-cli 远程启动:redis-cli -h host -p port -a password Redis 连接命令 1 AUTH password 验证密码是否正确 2 ECHO message 打印字符串 3 PING 查看服务是否运行 4 QUIT 关闭当前连接 5 SELECT index 切换…

UI设计案例,B端后台界面设计教程

B端产品是为“组织”提供服务,以业务为中心,追求时效性,在视觉上,内容为王,视觉为功能让步,追求简洁、清晰、克制、理性的视觉风格。B 端产品业务比较复杂,页面内容也会较多,B端界面…

Python与人工智能:气象领域的数据处理与模型优化

Python是功能强大、免费、开源,实现面向对象的编程语言,在数据处理、科学计算、数学建模、数据挖掘和数据可视化方面具备优异的性能,这些优势使得Python在气象、海洋、地理、气候、水文和生态等地学领域的科研和工程项目中得到广泛应用。可以…

LLM资料:中文embedding库

Highlight(重点提示) 理解LLM,就要理解Transformer,但其实最基础的还是要从词的embedding讲起。 毕竟计算机能处理的只有数字,所以万事开头的第一步就是将要处理的任务转换为数字。 面向中文的开源embedding库在自然…

MQ集合了

消息队列,FIFO :异步 解耦 削峰 复杂度上升 幂等 重复消费 消息丢失 / 可用性降低 mq故障 / 一致性要求 mq对比: activeMQ:jms规范,支持事务 xa协议 rabbitMQ:erlang 性能👌 高并发 多语…

react-router v6的Link组件relative属性解释

Link组件有一个名为relative的属性,值为route或path,默认为route 当Link的to为两个点时,配置relativeroute|path会有不同的效果, 之前由于路径嵌套不够深,看不出区别,于是尝试加深路径,一眼就看出了区别 官方解释 | React Router6 中文文档 下方代码请看根路径/cd及其二级路…

C++优先队列——priority_queue,函数对象,labmda表达式,pair等

头文件&#xff1a;#include<queue> 内部使用堆来实现&#xff0c;在需要或得最大的几个值或最小的几个值而不关心整个数组的顺序时非常好用。 用法&#xff1a; priority_queue<int, vector<int>, greater<int>>q; 第一个参数为堆中存储的元素。 …

vue 借助vue-amap插件对高德地图的简单使用

需求&#xff1a;实现点击获取经纬度、定位、对特殊位置标点及自定义信息窗体功能。 高德地图的官网API&#xff1a;概述-地图 JS API 1.4 | 高德地图API vue-amap的中文文档&#xff1a;组件 | vue-amap 实现&#xff1a; 1、安装vue-amap插件 npm install vue-amap --save…

AI预测福彩3D第20弹【2024年3月28日预测--第4套算法重新开始计算第6次测试】

今天继续对第4套算法进行测试&#xff0c;测试的目的主要是为了记录统计两套方案的稳定性和命中率&#xff0c;昨天的第二套方案已命中。今天是第5次测试&#xff0c;同样测试两个方案。废话不多说&#xff0c;直接上结果。 2024年3月28日福彩3D的七码预测结果如下 …

武忠祥《660题》高效刷题包+资料分享

660题的难度书虽然比较难&#xff0c;对于基础的考察比较深入&#xff0c;所以&#xff0c;有没有一种可能&#xff0c;做题太慢&#xff0c;是因为基础不好导致的&#xff01; 所以再继续做下去&#xff0c;就没有什么意义了&#xff0c;因为这就像是用一把钝刀去砍树&#x…

mybatis搭建开发环境

1.创建maven工程 2.配置pom.xml <!--数据库驱动--> <dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.47</version> </dependency> <!--Mybatis--> <depend…

vscode使用sftp上传

1.用vscode打开项目 2.安装一下这个sftp 3.使用快捷键 ctrlshiftP 打开指令窗口&#xff0c;输入 sftp:config&#xff0c;选中回车&#xff0c;在当前目录中会自动生成 .vscode 文件夹及 sftp.json 4.修改sftp.json文件配置&#xff0c;改成以下&#xff08;默认的参数可能上传…

八种顺序读写函数的介绍(fput/getc;fput/gets;fscanf,fprintf;fwrite,fread)

一&#xff1a;读写的含义的解释&#xff1a; 读&#xff08;读出&#xff09;&#xff1a;即从文件里面读出数据----------->和scanf从键盘里面读出数据类似 写&#xff08;写入&#xff09;&#xff1a;即把数据写入文件里面----------->和printf把数据写入到屏幕上类…

【leetcode】双“指针”

标题&#xff1a;【leetcode】双指针 水墨不写bug 我认为 讲清楚为什么要用双指针 比讲怎么用双指针更重要&#xff01; &#xff08;一&#xff09;快乐数 编写一个算法来判断一个数 n 是不是快乐数。 「快乐数」 定义为&#xff1a; 对于一个正整数&#xff0c;每一次将该数…

Unity 窗口化设置

在Unity中要实现窗口化&#xff0c;具体设置如下&#xff1a; 在编辑器中&#xff0c;选择File -> Build Settings。在Player Settings中&#xff0c;找到Resolution and Presentation部分。取消勾选"Fullscreen Mode"&#xff0c;并选择"Windowed"。设…

Linux:Jenkins:参数化版本回滚(6)

上几章我讲到了自动集成和部署 Linux&#xff1a;Jenkins全自动持续集成持续部署&#xff08;4&#xff09;-CSDN博客https://blog.csdn.net/w14768855/article/details/136977106 当我们觉得这个页面不行的时候&#xff0c;需要进行版本回滚&#xff0c;回滚方法我这里准备了…

Linux 反引号、单引号以及双引号的区别

1.单引号—— 单引号中所有的字符包括特殊字符&#xff08;$,,和\&#xff09;都将解释成字符本身而成为普通字符。它不会解析任何变量&#xff0c;元字符&#xff0c;通配符&#xff0c;转义符&#xff0c;只被当作字符串处理。 2.双引号——" 双引号&#xff0c;除了$,…

LangSAM项目优化,将SAM修改为MoblieSAM,提速5~6倍

Language Segment-Anything 是一个开源项目&#xff0c;它结合了实例分割和文本提示的强大功能&#xff0c;为图像中的特定对象生成蒙版。它建立在最近发布的 Meta 模型、segment-anything 和 GroundingDINO 检测模型之上&#xff0c;是一款易于使用且有效的对象检测和图像分割…

定时任务 之 cron 表达式

Cron 表达式产生的背景&#xff1a;在Unix系统中&#xff0c;用户经常需要设置一些周期性被执行的任务&#xff0c;如定期备份文件、发送邮件等。为了满足这种需求&#xff0c;Unix系统提供了crontab命令&#xff0c;允许用户定义任务的时间表&#xff0c;并在指定的时间点自动…