disgare 的博客
首页
博客
分类
标签
首页
博客
分类
标签
  • 网络

    • 计算机网络学习笔记
    • 网络安全相关
    • 域名和子网掩码
    • CORS 跨域资源共享
    • DNS、HTTP 与 HTTPS
    • Server-Sent Events (SSE)
    • WebSocket 长连接
  • 计算机基础

    • 操作系统 IO 相关知识
    • 操作系统学习笔记
    • 程序的机器级表示
    • 音频文件基础
    • 正则表达式相关概念
    • ffmpeg 的安装以及实现音频切分功能
    • Hex 和 Base64 编码
    • XML 的使用
  • 数据结构与算法

    • 动态规划算法学习笔记
    • 基于比较的排序算法的最坏情况下的最优下界为什么是O(nlogn)
    • 集合与数据结构学习笔记
    • 面试常见算法总结
    • 算法导论第二部分排序学习笔记
    • 算法导论第一部分学习笔记
  • Java

    • 对象之间的映射与转换
    • 反射学习笔记
    • 泛型相关概念
    • 关于 boolean 类型的坑
    • 如何使用 lambda 表达式实现排序
    • CompletableFuture 相关用法
    • CompletableFuture 源码浅要阅读
    • FutureTask 源码阅读
    • Guava 常用 API
    • Guava 源码阅读:Multimap 相关
    • Jackson 的各种使用
    • Java 的 Excel 相关操作
    • java 的常见性能问题分析以及出现场景
    • java 基础知识
    • JAVA 枚举的基础和原理
    • Java 图片文件上传下载处理
    • Java 序列化
    • Java 异常
    • Java 语法糖
    • Java 中关于字符串处理的常用方法
    • Java 中强、软、弱、虚引用
    • JAVA 注解小结
    • Java Http 访问框架
    • Java Stream 的使用
    • Java8 新特性
    • netty 学习笔记
    • Scanner 的各种用法
    • Servlet 学习笔记
    • String、StringBuffer、StringBuilder 学习笔记
  • JVM

    • 虚拟机执行子系统
    • JVM 自动内存管理
    • Linux 中 JVM 常用工具以及常见问题解决思路
  • Linux

    • crontab 表达式
    • Linux 常见命令
    • Linux 文件系统
  • 中间件

    • 关于定时任务原理
    • 详解 kafka
    • ES 搜索引擎
    • flink 提交流程
    • Grape-RAG
    • Hadoop 基础原理
  • 多线程

    • 多线程基础学习笔记
    • 简单了解并发集合
    • 如何手写单例
    • 深入理解 java 多线程安全
    • 生产者消费者问题
    • 线程池作用、用法以及原理
    • AQS 组件
    • ThreadLocal 原理以及使用
  • 非关系型数据库

    • Redis 集群
    • Redis 数据结构、对象与数据库
    • Redis 学习笔记
  • 关系型数据库

    • B+ 树的插入、删除和数据页分裂机制
    • MySQL 的 binglog、redolog、undolog
    • MySQL 的记录存储结构、存储引擎与 Buffer Pool
    • MySQL 基本的特性
    • MySQL 开发规范
    • MySQL 事务与锁与 MVCC
    • MySQL 数据类型、字符集相关内容
    • MySQL 索引与索引优化
    • PostgreSQL 更新数据时 HOT优化
    • PostgreSQL 相关用法
  • Python

    • Python 基础语法
    • Python 学习
  • Spring 项目

    • Lombok 的常用注解
    • maven 小结
    • MyBatis 框架的使用
    • MyBatis 重要知识点总结
    • MybatisPlus 的使用
    • Spring 框架基础使用
    • Spring 事务相关
    • Spring IOC 的原理及源码
    • Spring AOP 的使用和原理
    • SpringBoot 的原理
    • SpringBoot 基础使用
    • SpringWeb 重要知识点
  • 分布式

    • 初步了解 docker
    • 从 ACID 到 BASE 事务处理的实现
    • 访问远程服务
    • 分布式 id
    • 分布式缓存相关问题
    • 分布式集群理论和分布式事务协议
    • 分布式架构的观测
    • 分布式一致性算法
    • 负载均衡 Load Balancing
    • 关于分布式系统 RPC 中高可用功能的实现
    • 集群间数据同步的目的
    • 三高问题下的系统优化
    • 数据库分库分表
    • 详解 Spring Cloud
    • Dubbo 基础概念
    • Gossip 协议
    • nginx 学习笔记
    • Protobuf 通信协议
    • Zookeeper 基础学习
      • ZK 是干什么的
      • 数据结构
        • 结构
        • 类型
        • stat 与 data
      • 监听器 watcher
      • 集群
        • 架构
        • 选举机制
        • 一定要配置奇数台机器
      • 一致性协议
        • 如何知道对方机器是否出现故障
        • Paxos 算法
        • ZAB 协议
        • 如何保证事务的顺序一致性
      • 应用场景
        • 分布式锁
        • 注册中心
        • 命名服务
  • 架构设计

    • 参数校验与异常处理
    • 抽象方法与设计模式
    • 代码整洁之道
    • 权限系统设计
    • 用低内存处理大量数据
    • 设计模式——策略模式
    • 设计模式——过滤器模式在 Spring 中的实践
    • 状态模式
    • 统一结果返回
    • 为什么要打日志?怎么打日志?打什么日志?
    • 运维监控常见指标含义
    • 资深研发进阶
    • DDD 架构学习笔记
    • Java 常用的规则引擎
    • MVC 架构学习笔记
  • AI

    • 如何编写 Prompt
    • Agent 工程架构
    • LLM 相关内容
    • NLP 相关知识
    • vibe coding 最佳实践
    • windows 下 ollama 迁移到 D 盘
  • 开发工具

    • 如何画时序图、流程图、状态流转图
    • excel 关于 =vlookup 的用法
    • git 的学习以及使用
    • IDEA 插件推荐
    • IDEA 常用快捷键以及调试
    • Shell 脚本
    • swagger 的使用
  • 前端

    • 简单了解前端页面开发
    • 伪静态是什么
    • GitHub Pages 部署教程
    • Vercel 部署教程
    • vue-admin-template 简单使用
    • VuePress 博客搭建指南
  • 项目

    • 面试刷题网——技术方案
    • 影视资源聚合站——技术方案
  • 问题记录

    • 定时任务单线程消费 redis 中数据导致消费能力不足
    • 提供可传递的易受攻击的依赖项
    • Liteflow 在 SpringBoot 启动时无法注入组件问题 couldn‘t find chain with the id[THEN(NodeComponent)]
  • 金融

    • 股票分析——关于电力
    • 股票技术面——量价关系
    • 股票技术面——盘口
    • 股票技术面——基础
    • 基础的金融知识
    • 基金与股票
    • 韭菜的自我总结
    • 聊聊价值投资
  • 其他

    • 程序员职场工作需要注意什么
    • 创业全链路SOP:从灵光一现到系统化增长的实战指南
    • 观罗翔讲刑法随笔
    • 价格和价值
    • 立直麻将牌效益理论
    • 梅花易数学习笔记
    • 压力管理
2021-12-03
分布式
目录

Zookeeper 基础学习

# ZK 是干什么的

分布式解决了用户量不断增加的问题,但是分布式所带来的问题也不少,比如在一个分布式系统中如何确保被 RPC 调用的机器存活呢?使用 TCP 三次握手来解决?不行,速度太慢。为此我们去使用一些机器来通知调用方被调用方是否存活

那这些机器需要什么功能呢,首先它们应该保证高可用,如果自身的高可用都不能保证又如何去维持分布式程序的高可用呢,因此它们应该进行集群配置,并且集群应该保证强一致性

为了管理分布式程序的各个机器,它们需要存放一些代表各个机器的数据,并且读数据的速度要快,因此他们的数据应该存放在内存中

最后它应该有一些通知回调功能,当被调用方挂了应该及时告知调用方发生了什么事情

最后,我们得出结论,ZK 提供分布式协同服务,解决出现在分布式系统中出现的一系列问题,它的设计目标是将那些复杂且容易出错的分布式一致性服务封装起来,构成一个高效可靠的原语集,并以一系列简单易用的接口提供给用户使用

ZooKeeper 将数据保存在内存中,性能是非常棒的。 在读多于写的应用程序中尤其地高性能,因为写会导致所有的服务器间同步状态

拥有这些特性,它不止能做注册中心,还可以做分布式锁、命名服务等

# 数据结构

# 结构

使用多叉树结构,每个节点都可以存放数据,不过可以存放的数据比较少,最多只有1mb。它的数据存放在内存中,因此性能很好

zookeeper 中的节点叫 znode,根节点叫/,下面的节点一般以 /XXX,/XXX/XX 来命名,类似文件系统

# 类型

znode 分为两大类型(持久节点、短暂节点),四种类型,分别是:

  • 持久:这个节点一直存放在 zookeeper 中,直到被 delete
  • 短暂:这个节点被某个会话写入,当连接断开,这个节点消失,这个节点不可以创建子节点(session 被看作一个 tcp 长连接)
  • 持久编号:这个节点有自己的 id 和持久节点的性质
  • 短暂编号:这个节点有自己的 id 和短暂节点的性质

# stat 与 data

在 Linux 中这个命令用来获取文件属性,在 ZK 中做为一个名词也是相似的功能

每个 znode 除了存放数据,还会自动维持一个叫 stat 的数据结构,stat 中存放了这个节点的所有信息,比如事务 ID、节点创建时间、当前节点的子节点个数等

data 指的是节点存放数据的具体内容

# 监听器 watcher

监听器是观察者模式的一种实现,观察者模式指在出现一对多关系依赖的时候,会有一个观察者或者一群观察者在旁边观察,如果某个被依赖对象出现修改时,观察者会自动通知依赖它的对象 image-2026-01-31-21-09-13.png zkClent 代表客户端,它会创建一个 connect 连接 zookeeper,还有一个端口用来接受 zookeeper 传来的消息

客户端可以指定它要监听哪一个节点,当这个节点或者子节点发生变化时(可能是节点中数据变化或者节点直接变化),zookeeper 返回消息给客户端

zookeeper 使用一个链表来存放客户端以及它对应监听的节点

以上就是 zk 作为注册中心的原理,很简单吧,服务提供者在 zk 中对应一个个暂时节点,服务调用者会起一个 watcher 来监听这些节点,如果节点挂掉,暂时节点就会被删除,zk 会通知调用者

# 集群

为了保证高可用,可以以集群形态来部署 zookeeper,集群中使用 ZAB 协议保证数据一致性

# 架构

与传统的主从模式不同,ZK 使用 Leader、Follower、Observer 来实现集群

  • Leader 用来实现数据的写入和读取,主要负责写入,一个集群中只有一个 leader
  • Follower 只能进行数据的读取,如果客户端需要实现写入请求时,它会转交给 Leader 实现
  • Observer 用来实现数据的读取,它不参加选举以及半数相关的操作,它用来提高集群数据读取效率。它可以处理客户端的读请求,并且会将写请求转发给 Leader,用于提高 zk 的吞吐量

# 选举机制

ZK 优先保证一致性(C)和分区容错性(P),而非可用性(A)

当 Leader 挂掉或者集群建立的时候需要选择 Leader,只有当前集群存活大于一半的主机才会执行此步骤,此时所有的服务器会按一定顺序进行选举,此时所有的机器处于 LOOKING 状态

选举机制的过程如下:节点进入 LOOKING 状态,向其他节点发送投票信息,这里包含自己的 myid、zxid、epoch,这三玩意是啥呢

  • myid:每个节点的唯一标识(数字)
  • zxid:事务 ID(64 位数字,高 32 位是 epoch,低 32 位是计数器)
  • epoch:逻辑时钟,每次选举递增

然后每台机器都接收其他节点的投票,根据选举规则比较投票。优先比较 epoch,如果 epoch 一样比较 zxid,然后是 myid。这里也体现了 paxos 的思路,最大事务优先,然后比较机器 id

选举完毕 Leader 全量复制自己的数据给所有 Follower,复制完成后对外提供服务,如果有数据写入会进行增量复制

zk 集群如果出现了分区,过半机器会重新选举,同时只有超过半数节点存活的集群分区才能继续提供服务

# 一定要配置奇数台机器

zookeeper 集群中有这样一个特性,如果这个集群中超过一半的机器可用,这个集群可用。此外,选举中半数投票、半数写成功策略也是一半可以进行

因为这些特性,配置5台机器和配置6台机器允许的出错数量是一样的,所以配置奇数台机器可以省下一台机器的钱

同时,ZooKeeper 选举的过半机制还防止了脑裂。比如现在有一个由 6 台服务器所组成的一个集群,部署在了 2 个机房,每个机房 3 台。正常情况下只有 1 个 leader,但是当两个机房中间网络断开的时候,每个机房的 3 台服务器都会认为另一个机房的 3 台服务器下线,而选出自己的 leader 并对外提供服务。若没有过半机制,当网络恢复的时候会发现有 2 个 leader。仿佛是 1 个大脑(leader)分散成了 2 个大脑,这就发生了脑裂现象。脑裂期间 2 个大脑都可能对外提供了服务,这将会带来数据一致性等问题

ZooKeeper 的过半机制导致不可能产生 2 个 leader,因为少于等于一半是不可能产生 leader 的

# 一致性协议

在分布式环境中,一致性是指数据在多个副本之间是否能够保持一致的特性。 如果对第一个节点的数据进行了更新操作并且更新成功后,却没有使得第二个节点上的数据得到相应的更新,于是在对第二个节点的数据进行读取操作时,获取的依然是老数据(或称为脏数据),这就是典型的分布式数据不一致情况

一致性协议有一个重要前提,在不可靠信道上试图通过消息传递的方式达到一致性是不可能的

# 如何知道对方机器是否出现故障

zookeeper 有一个心跳帧(可以在配置文件中修改),默认为2秒,当机器之间发送消息的时候重新计时

在初始化连接时,超过10个心跳没有回应则认为对方挂了,在配置文件的对应参数为 initlimit

在正常连接时,超过5个心跳没有回应则认为对方挂了,在配置文件的对应参数为 synlimit

# Paxos 算法

Paxos 算法是基于消息传递且具有高度容错特性的一致性算法,是目前公认的解决分布式一致性问题最有效的算法之一

这个算法主要由提案者、表决者、学习者组成

首先每个提案者在提出提案时都会首先获取到一个具有全局唯一性的、递增的提案编号 N,然后将这个编号发送给所有的表决者

每个表决者中保存的已经被接受的提案中会存在一个编号最大的提案,将这个编号返回给提案者

如果提案者接受到了半数以上的回应,将这个提案的内容发送给所有表决者,表决者只会同意大于等于自身编号最大的提案,如果提案者接受到了半数以上的同意,向未批准的表决者发送提案内容和提案编号并让它无条件执行和提交

如果这个过程失败会递增该提案的编号

# ZAB 协议

ZAB 协议由 paxos 算法改编而来

简单来说就是进行修改时 Leader 问 Followers 是否同意更新,如果超过半数以上的同意那么就进行 Follower 和 Observer 的更新

Leader 内部维持着一个队列,在数据更新时会按照先进先出的形式发送数据给 Follower,这是因为请求处理的顺序不同就会导致数据的不同,从而产生数据不一致问题

ZAB 协议有两种模式:

  • 崩溃恢复:当 Leader 崩溃后的选主过程,当选举产生了新的 Leader 服务器,同时集群中已经有过半的机器与该 Leader 服务器完成了状态同步之后,ZAB 协议就会退出恢复模式。上文已经说过了
  • 消息广播:集群中对数据同步的过程,是 2PC 的优化版本

消息广播的基本流程如下,客户端请求接收后会路由到 leader 节点,Leader 节点接收客户端写请求后再让,每个请求被赋予一个全局唯一且递增的 ZXID(事务 ID),然后向所有 follower 节点发送这个提案

从节点收到后会先写入本地事务日志,然后向 Leader 发送 ACK 确认。这里的事务日志是每个 ZK 节点上的二进制文件,不是 znode,数据仅在内存树(DataTree)中生效后才对客户端可见。事务日志只是预写日志(WAL),不直接影响服务数据

当 Leader 收到超过半数 Follower 的 ACK 后,向所有 Follower 发送 COMMIT 指令,同时自己也提交该事务。这时候访问 zk 就可以观察到这个新 znode 了

# 如何保证事务的顺序一致性

ZK 需要保证事务的顺序一致性,这是因为 ZK 是一个分布式系统,事务的执行顺序不能被打乱。举个例子,ZK 有很多的 Znode,每个 Znode 都有一个唯一的路径,比如 /a/b/c,当客户端对 /a/b/c 进行写操作时,ZK 会先检查 /a/b 是否存在,如果不存在就会创建 /a/b,然后再创建 /a/b/c。这时候如果有其他客户端对 /a/b 进行删除操作,那么 /a/b/c 就不能被创建,否则就会导致数据不一致问题

那 ZK 是如何处理该问题的呢?

Zookeeper 采用了全局递增的事务 Id 来标识,所有的 proposal(提议)都在被提出的时候加上了 zxid,zxid 实际上是一个 64 位的数字,高 32 位是 epoch 用来标识 leader 周期,如果有新的 leader 产生出来,epoch 会自增

低 32 位用来递增计数,代表了提案 ID。当新产生 proposal 的时候,会依据数据库的两阶段过程,首先会向其他的 server 发出事务执行请求,如果超过半数的机器都能执行并且能够成功,那么就会开始执行

此外,ZK 还有一个 FIFO 队列,Leader 对每个 Follower 都建立了一个 FIFO 队列。所有的 proposal 都按照顺序进入这个队列,等待被处理。这个队列底层是使用 TCP 的连接来实现的,这就保证了事务的顺序一致性。

# 应用场景

# 分布式锁

zookeeper 在多线程情况下只会创建全局的唯一节点

利用这个特性,将某个临时节点当作锁,一个连接创建这个节点代表这个连接抢到了锁,其他链接需要等待,当这个连接下线或者删除节点时(redis 中需要考虑连接下线情况),锁被释放

# 注册中心

可以作为 dubbo、spring cloud、kafka 等集群的注册中心使用,每个服务器创建临时节点,里面存放可提供的服务,web 网页作为客户端连接 zookeeper,在需要时查找自己想要的服务,将服务器的IP、方法等信息存放到自己的缓存中

客户端使用 watcher 监视 zookeeper 中的节点,如果服务器挂掉,zookeeper 会发送消息给客户端(不使用 watcher 的话 zookeeper 也可以自动发送替代的服务器信息给客户端)

那是不是只要是个可以存数据的地方都可以做注册中心呢,非也:

ZK 使用 ZAB 协议(Zookeeper Atomic Broadcast)确保所有节点的数据一致,即使部分节点宕机,只要集群存活过半,仍能保证数据正确性

并且 zk 的 watch 监控功能,有回调机制。服务注册后,如果服务宕机,消费者能立即感知服务下线

对比一下 Redis,redis 无原生临时键,需依赖 Key+TTL 模拟临时节点,但 TTL 到期后仍需消费者主动轮询或依赖额外机制比如轮询通知集群变化,复杂度较高

这里 redis 默认是 ap 架构,因为主从同步默认是异步的,但是可以设置成全同步,改成 cp 架构

# 命名服务

对于每个节点的全路径,它必定是唯一的,我们可以使用节点的全路径作为命名方式了。而且更重要的是,路径是我们可以自己定义的,这对于我们对有些有语意的对象的 ID 设置可以更加便于理解

#Zookeeper
最后更新: 2/25/2026, 5:21:35 AM
Protobuf 通信协议
参数校验与异常处理

← Protobuf 通信协议 参数校验与异常处理→

最近更新
01
vibe coding 最佳实践
02-24
02
立直麻将牌效益理论
02-23
03
伪静态是什么
02-08
更多文章>
Theme by Vdoing
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式