庖丁解牛:Kafka 中 ZooKeeper 的核心作用与依赖剖析
-
- 1. 引言:Kafka 与 ZooKeeper 的“黄金搭档”时代
- 2. 全景架构:ZooKeeper 在 Kafka 中的位置
- 3. ZooKeeper 的六大核心作用
-
- 作用一:Broker 注册与发现 —— 动态感知节点存活
- 作用二:Topic 配置管理 —— 存储元数据
- 作用三:Controller 选举 —— 集群的“总指挥”如何产生
- 作用四:集群元数据管理 —— 全局配置的“注册中心”
- 作用五:Consumer Group 协调 —— 位移管理(旧版 Kafka)
- 作用六:故障检测与通知 —— 基于 Watch 的事件机制
- 4. 完整数据流:创建 Topic 时 ZooKeeper 做了什么?
- 5. ZooKeeper 模式下 Kafka 的依赖与问题
-
- 5.1 依赖关系图
- 5.2 ZooKeeper 模式的已知痛点
- 6. 对比表:ZooKeeper 依赖 vs. 自研 KRaft
- 7. 实践:如何查看 Kafka 在 ZooKeeper 中存储的数据?
-
- 7.1 连接 ZooKeeper
- 7.2 关键节点速查表
- 8. 总结:ZooKeeper 的功与过
|
🌺The Begin🌺点点关注,收藏不迷路🌺 |
在 KRaft 全面普及前,ZooKeeper 曾是 Kafka 不可或缺的“大脑”
1. 引言:Kafka 与 ZooKeeper 的“黄金搭档”时代
如果你维护过 Kafka 2.x 及更早版本的集群,一定会发现一个“奇怪”的现象:启动 Kafka 之前,必须先启动 ZooKeeper。
为什么一个消息队列系统要依赖一个独立的分布式协调服务?ZooKeeper 到底为 Kafka 承担了哪些不可替代的职责?
本文将深入剖析 ZooKeeper 在 Kafka 中的六大核心作用,并解释为什么 Kafka 最终决定“抛弃”它——了解这些,才能理解 KRaft 架构的演进逻辑。
2. 全景架构:ZooKeeper 在 Kafka 中的位置
#mermaid-svg-FZKrYGbdWseoYLfx{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-FZKrYGbdWseoYLfx .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-FZKrYGbdWseoYLfx .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-FZKrYGbdWseoYLfx .error-icon{fill:#552222;}#mermaid-svg-FZKrYGbdWseoYLfx .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-FZKrYGbdWseoYLfx .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-FZKrYGbdWseoYLfx .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-FZKrYGbdWseoYLfx .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-FZKrYGbdWseoYLfx .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-FZKrYGbdWseoYLfx .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-FZKrYGbdWseoYLfx .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-FZKrYGbdWseoYLfx .marker{fill:#333333;stroke:#333333;}#mermaid-svg-FZKrYGbdWseoYLfx .marker.cross{stroke:#333333;}#mermaid-svg-FZKrYGbdWseoYLfx svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-FZKrYGbdWseoYLfx p{margin:0;}#mermaid-svg-FZKrYGbdWseoYLfx .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-FZKrYGbdWseoYLfx .cluster-label text{fill:#333;}#mermaid-svg-FZKrYGbdWseoYLfx .cluster-label span{color:#333;}#mermaid-svg-FZKrYGbdWseoYLfx .cluster-label span p{background-color:transparent;}#mermaid-svg-FZKrYGbdWseoYLfx .label text,#mermaid-svg-FZKrYGbdWseoYLfx span{fill:#333;color:#333;}#mermaid-svg-FZKrYGbdWseoYLfx .node rect,#mermaid-svg-FZKrYGbdWseoYLfx .node circle,#mermaid-svg-FZKrYGbdWseoYLfx .node ellipse,#mermaid-svg-FZKrYGbdWseoYLfx .node polygon,#mermaid-svg-FZKrYGbdWseoYLfx .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-FZKrYGbdWseoYLfx .rough-node .label text,#mermaid-svg-FZKrYGbdWseoYLfx .node .label text,#mermaid-svg-FZKrYGbdWseoYLfx .image-shape .label,#mermaid-svg-FZKrYGbdWseoYLfx .icon-shape .label{text-anchor:middle;}#mermaid-svg-FZKrYGbdWseoYLfx .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-FZKrYGbdWseoYLfx .rough-node .label,#mermaid-svg-FZKrYGbdWseoYLfx .node .label,#mermaid-svg-FZKrYGbdWseoYLfx .image-shape .label,#mermaid-svg-FZKrYGbdWseoYLfx .icon-shape .label{text-align:center;}#mermaid-svg-FZKrYGbdWseoYLfx .node.clickable{cursor:pointer;}#mermaid-svg-FZKrYGbdWseoYLfx .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-FZKrYGbdWseoYLfx .arrowheadPath{fill:#333333;}#mermaid-svg-FZKrYGbdWseoYLfx .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-FZKrYGbdWseoYLfx .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-FZKrYGbdWseoYLfx .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-FZKrYGbdWseoYLfx .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-FZKrYGbdWseoYLfx .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-FZKrYGbdWseoYLfx .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-FZKrYGbdWseoYLfx .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-FZKrYGbdWseoYLfx .cluster text{fill:#333;}#mermaid-svg-FZKrYGbdWseoYLfx .cluster span{color:#333;}#mermaid-svg-FZKrYGbdWseoYLfx div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-FZKrYGbdWseoYLfx .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-FZKrYGbdWseoYLfx rect.text{fill:none;stroke-width:0;}#mermaid-svg-FZKrYGbdWseoYLfx .icon-shape,#mermaid-svg-FZKrYGbdWseoYLfx .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-FZKrYGbdWseoYLfx .icon-shape p,#mermaid-svg-FZKrYGbdWseoYLfx .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-FZKrYGbdWseoYLfx .icon-shape .label rect,#mermaid-svg-FZKrYGbdWseoYLfx .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-FZKrYGbdWseoYLfx .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-FZKrYGbdWseoYLfx .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-FZKrYGbdWseoYLfx :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
客户端
Kafka Cluster
ZooKeeper Ensemble
元数据读写
注册/心跳
注册/心跳
注册/心跳
Leader 选举
分区状态同步
副本管理
写入
消费
管理操作
ZooKeeper Leader
ZooKeeper Follower
ZooKeeper Follower
Controller Broker
Broker 1
Broker 2
Broker 3
Producer
Consumer
Admin Tool
核心定位:ZooKeeper 是 Kafka 集群的元数据存储中心和分布式协调基石。
3. ZooKeeper 的六大核心作用
作用一:Broker 注册与发现 —— 动态感知节点存活
每个 Broker 启动时,会在 ZooKeeper 的 /brokers/ids 路径下创建一个临时节点:
渲染错误: Mermaid 渲染失败: Parse error on line 2: …LR subgraph ZK路径[/brokers/ids] ———————-^ Expecting 'SEMI', 'NEWLINE', 'SPACE', 'EOF', 'GRAPH', 'DIR', 'subgraph', 'SQS', 'end', 'AMP', 'COLON', 'START_LINK', 'STYLE', 'LINKSTYLE', 'CLASSDEF', 'CLASS', 'CLICK', 'DOWN', 'UP', 'NUM', 'NODE_STRING', 'BRKT', 'MINUS', 'MULT', 'UNICODE_TEXT', got 'TRAPSTART'
存储内容:每个 Broker 节点的数据包含主机名、端口、机架信息等元数据。
临时节点的妙用:
- Broker 正常关闭 → 主动删除节点
- Broker 宕机/网络分区 → ZooKeeper 会话超时 → 自动删除节点
Controller 通过监听 /brokers/ids 的子节点变化,能实时感知 Broker 的上线与下线。
作用二:Topic 配置管理 —— 存储元数据
Topic 的元数据存储在 ZooKeeper 的 /brokers/topics 路径下:
渲染错误: Mermaid 渲染失败: Lexical error on line 2. Unrecognized text. …art TD subgraph “ZK路径: /brokers/topi ———————-^
存储的关键信息:
| 分区数量 | Topic 有多少个分区 | num_partitions: 6 |
| 副本分配 | 每个分区的副本分布在哪些 Broker | replicas: [1,2,3] |
| Leader 副本 | 分区的 Leader 在哪个 Broker | leader: 1 |
| ISR 列表 | 与 Leader 保持同步的副本集合 | isr: [1,2] |
| 配置覆盖 | Topic 级别的特殊配置 | min.insync.replicas: 2 |
作用三:Controller 选举 —— 集群的“总指挥”如何产生
Kafka 集群中必须有一个 Broker 担任 Controller,负责管理分区分配、副本故障转移等任务。
选举机制:
ZooKeeper
Broker 3
Broker 2
Broker 1
ZooKeeper
Broker 3
Broker 2
Broker 1
#mermaid-svg-NbvlrUkIdktTnFbJ{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-NbvlrUkIdktTnFbJ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-NbvlrUkIdktTnFbJ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-NbvlrUkIdktTnFbJ .error-icon{fill:#552222;}#mermaid-svg-NbvlrUkIdktTnFbJ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-NbvlrUkIdktTnFbJ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-NbvlrUkIdktTnFbJ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-NbvlrUkIdktTnFbJ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-NbvlrUkIdktTnFbJ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-NbvlrUkIdktTnFbJ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-NbvlrUkIdktTnFbJ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-NbvlrUkIdktTnFbJ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-NbvlrUkIdktTnFbJ .marker.cross{stroke:#333333;}#mermaid-svg-NbvlrUkIdktTnFbJ svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-NbvlrUkIdktTnFbJ p{margin:0;}#mermaid-svg-NbvlrUkIdktTnFbJ .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-NbvlrUkIdktTnFbJ text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-NbvlrUkIdktTnFbJ .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-NbvlrUkIdktTnFbJ .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-NbvlrUkIdktTnFbJ .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-NbvlrUkIdktTnFbJ .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-NbvlrUkIdktTnFbJ #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-NbvlrUkIdktTnFbJ .sequenceNumber{fill:white;}#mermaid-svg-NbvlrUkIdktTnFbJ #sequencenumber{fill:#333;}#mermaid-svg-NbvlrUkIdktTnFbJ #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-NbvlrUkIdktTnFbJ .messageText{fill:#333;stroke:none;}#mermaid-svg-NbvlrUkIdktTnFbJ .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-NbvlrUkIdktTnFbJ .labelText,#mermaid-svg-NbvlrUkIdktTnFbJ .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-NbvlrUkIdktTnFbJ .loopText,#mermaid-svg-NbvlrUkIdktTnFbJ .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-NbvlrUkIdktTnFbJ .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-NbvlrUkIdktTnFbJ .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-NbvlrUkIdktTnFbJ .noteText,#mermaid-svg-NbvlrUkIdktTnFbJ .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-NbvlrUkIdktTnFbJ .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-NbvlrUkIdktTnFbJ .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-NbvlrUkIdktTnFbJ .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-NbvlrUkIdktTnFbJ .actorPopupMenu{position:absolute;}#mermaid-svg-NbvlrUkIdktTnFbJ .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-NbvlrUkIdktTnFbJ .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-NbvlrUkIdktTnFbJ .actor-man circle,#mermaid-svg-NbvlrUkIdktTnFbJ line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-NbvlrUkIdktTnFbJ :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
启动或发现 Controller 故障
Controller 在 /controller 节点
记录自己的 brokerId
尝试创建 /controller
尝试创建 /controller
尝试创建 /controller
创建成功 → 成为 Controller
创建失败 → 成为 Follower
创建失败 → 成为 Follower
选举特点:
- 采用 ZooKeeper 的临时节点 + 原子创建机制实现
- 最先成功创建 /controller 节点的 Broker 成为 Controller
- 其他 Broker 监听该节点,当 Controller 宕机时触发重新选举
作用四:集群元数据管理 —— 全局配置的“注册中心”
渲染错误: Mermaid 渲染失败: Lexical error on line 2. Unrecognized text. …art LR subgraph “ZooKeeper 存储的元数据” ———————-^
| /cluster/id | 集群唯一标识,用于跨集群识别 |
| /config/topics | Topic 级别的配置覆盖(如 retention.ms) |
| /config/clients | 客户端限流配额配置 |
| /config/users | 用户认证和 ACL 权限信息 |
| /admin/reassign_partitions | 分区迁移/重分配任务状态 |
作用五:Consumer Group 协调 —— 位移管理(旧版 Kafka)
⚠️ 重要提示:Kafka 0.9 版本后,Consumer Group 的位移管理已逐步迁移到内部主题 __consumer_offsets。但在 2.x 及更早版本中,部分元数据仍在 ZK 中。
渲染错误: Mermaid 渲染失败: Lexical error on line 2. Unrecognized text. …art TD subgraph “旧版路径 /consumers” ———————-^
旧版 ZK 的职责(Kafka 0.8 及以前):
- 存储 Consumer Group 的位移 offset
- 存储消费者与分区的分配关系(Owner)
痛点:ZooKeeper 不擅长高吞吐写入,大规模消费者会压垮 ZK。
作用六:故障检测与通知 —— 基于 Watch 的事件机制
ZooKeeper 的 Watch 机制是 Kafka 实现动态响应的核心:
Broker
ZooKeeper
Controller
Broker
ZooKeeper
Controller
#mermaid-svg-Nqwau0V3QlemL8li{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Nqwau0V3QlemL8li .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Nqwau0V3QlemL8li .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Nqwau0V3QlemL8li .error-icon{fill:#552222;}#mermaid-svg-Nqwau0V3QlemL8li .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Nqwau0V3QlemL8li .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Nqwau0V3QlemL8li .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Nqwau0V3QlemL8li .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Nqwau0V3QlemL8li .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Nqwau0V3QlemL8li .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Nqwau0V3QlemL8li .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Nqwau0V3QlemL8li .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Nqwau0V3QlemL8li .marker.cross{stroke:#333333;}#mermaid-svg-Nqwau0V3QlemL8li svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Nqwau0V3QlemL8li p{margin:0;}#mermaid-svg-Nqwau0V3QlemL8li .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-Nqwau0V3QlemL8li text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-Nqwau0V3QlemL8li .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-Nqwau0V3QlemL8li .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-Nqwau0V3QlemL8li .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-Nqwau0V3QlemL8li .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-Nqwau0V3QlemL8li #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-Nqwau0V3QlemL8li .sequenceNumber{fill:white;}#mermaid-svg-Nqwau0V3QlemL8li #sequencenumber{fill:#333;}#mermaid-svg-Nqwau0V3QlemL8li #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-Nqwau0V3QlemL8li .messageText{fill:#333;stroke:none;}#mermaid-svg-Nqwau0V3QlemL8li .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-Nqwau0V3QlemL8li .labelText,#mermaid-svg-Nqwau0V3QlemL8li .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-Nqwau0V3QlemL8li .loopText,#mermaid-svg-Nqwau0V3QlemL8li .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-Nqwau0V3QlemL8li .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-Nqwau0V3QlemL8li .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-Nqwau0V3QlemL8li .noteText,#mermaid-svg-Nqwau0V3QlemL8li .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-Nqwau0V3QlemL8li .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-Nqwau0V3QlemL8li .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-Nqwau0V3QlemL8li .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-Nqwau0V3QlemL8li .actorPopupMenu{position:absolute;}#mermaid-svg-Nqwau0V3QlemL8li .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-Nqwau0V3QlemL8li .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-Nqwau0V3QlemL8li .actor-man circle,#mermaid-svg-Nqwau0V3QlemL8li line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-Nqwau0V3QlemL8li :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
loop
[监听子节点变化]
注册 Watch on /brokers/ids
Broker 2 下线
Watch 触发,通知事件
触发故障处理:
重分配分区 Leader
更新 /brokers/topics 元数据
Watch 的应用场景:
| /brokers/ids | Broker 上线/下线 | Controller 更新集群节点列表 |
| /brokers/topics | Topic 创建/删除 | Controller 分配/回收分区 |
| /controller | Controller 变更 | 新 Controller 加载全量元数据 |
| /admin/reassign_partitions | 分区重分配任务 | Controller 执行分区迁移 |
4. 完整数据流:创建 Topic 时 ZooKeeper 做了什么?
Broker 3
Broker 2
Broker 1
Controller
ZooKeeper
Admin/命令行
Broker 3
Broker 2
Broker 1
Controller
ZooKeeper
Admin/命令行
#mermaid-svg-v5gMco5cBdInYAIh{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-v5gMco5cBdInYAIh .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-v5gMco5cBdInYAIh .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-v5gMco5cBdInYAIh .error-icon{fill:#552222;}#mermaid-svg-v5gMco5cBdInYAIh .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-v5gMco5cBdInYAIh .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-v5gMco5cBdInYAIh .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-v5gMco5cBdInYAIh .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-v5gMco5cBdInYAIh .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-v5gMco5cBdInYAIh .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-v5gMco5cBdInYAIh .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-v5gMco5cBdInYAIh .marker{fill:#333333;stroke:#333333;}#mermaid-svg-v5gMco5cBdInYAIh .marker.cross{stroke:#333333;}#mermaid-svg-v5gMco5cBdInYAIh svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-v5gMco5cBdInYAIh p{margin:0;}#mermaid-svg-v5gMco5cBdInYAIh .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-v5gMco5cBdInYAIh text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-v5gMco5cBdInYAIh .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-v5gMco5cBdInYAIh .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-v5gMco5cBdInYAIh .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-v5gMco5cBdInYAIh .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-v5gMco5cBdInYAIh #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-v5gMco5cBdInYAIh .sequenceNumber{fill:white;}#mermaid-svg-v5gMco5cBdInYAIh #sequencenumber{fill:#333;}#mermaid-svg-v5gMco5cBdInYAIh #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-v5gMco5cBdInYAIh .messageText{fill:#333;stroke:none;}#mermaid-svg-v5gMco5cBdInYAIh .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-v5gMco5cBdInYAIh .labelText,#mermaid-svg-v5gMco5cBdInYAIh .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-v5gMco5cBdInYAIh .loopText,#mermaid-svg-v5gMco5cBdInYAIh .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-v5gMco5cBdInYAIh .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-v5gMco5cBdInYAIh .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-v5gMco5cBdInYAIh .noteText,#mermaid-svg-v5gMco5cBdInYAIh .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-v5gMco5cBdInYAIh .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-v5gMco5cBdInYAIh .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-v5gMco5cBdInYAIh .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-v5gMco5cBdInYAIh .actorPopupMenu{position:absolute;}#mermaid-svg-v5gMco5cBdInYAIh .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-v5gMco5cBdInYAIh .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-v5gMco5cBdInYAIh .actor-man circle,#mermaid-svg-v5gMco5cBdInYAIh line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-v5gMco5cBdInYAIh :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
2. 数据持久化
1. 创建 /brokers/topics/my_topic
写入分区分配方案
3. Watch 触发 → 通知 Topic 创建
4. 解析分配方案
5. 创建分区副本
(Leader 角色)
5. 创建分区副本
(Follower 角色)
5. 创建分区副本
(Follower 角色)
6. 确认创建完成
7. 更新 Leader/ISR 信息
8. 创建完成
5. ZooKeeper 模式下 Kafka 的依赖与问题
5.1 依赖关系图
渲染错误: Mermaid 渲染失败: Lexical error on line 2. Unrecognized text. …art TD subgraph “Kafka 功能” K ———————-^
5.2 ZooKeeper 模式的已知痛点
| 运维复杂度高 | 需独立部署和维护 ZK 集群 | 两个系统的监控、升级、备份各一套 |
| 元数据写入瓶颈 | ZK 写性能上限约 10k QPS | 大规模 Topic 创建/分区重分配慢 |
| 分区数量受限 | 超过 20 万分区后 ZK 压力剧增 | 限制了 Kafka 集群规模 |
| Controller 恢复慢 | 故障时需从 ZK 全量加载元数据 | 故障切换耗时秒级到分钟级 |
| Watch 羊群效应 | 大量 Broker 同时收到 Watch 通知 | 瞬时网络风暴 |
| 脑裂风险 | ZK 与 Kafka 各自独立的选举机制 | 罕见但存在双 Controller 风险 |
6. 对比表:ZooKeeper 依赖 vs. 自研 KRaft
| 组件数量 | 2 套系统 | 1 套系统 |
| 元数据存储 | ZooKeeper 树形结构 | Kafka 内部主题 __cluster_metadata |
| 一致性协议 | ZAB(ZooKeeper) | Raft(内置) |
| Controller 选举 | ZK 临时节点 | Raft 选举 |
| Broker 注册 | ZK 临时节点 | Controller 注册 |
| 分区数上限 | ~20 万 | 百万级 |
| 恢复时间 | 秒级~分钟级 | 毫秒级 |
| 运维复杂度 | 高 | 低 |
7. 实践:如何查看 Kafka 在 ZooKeeper 中存储的数据?
7.1 连接 ZooKeeper
# 连接到 ZK 客户端
zkCli.sh -server localhost:2181
# 查看根路径
ls /
# 输出:[admin, brokers, cluster, config, controller, consumers, isr_change_notification]
# 查看所有 Broker
ls /brokers/ids
# 查看某个 Topic 的分区信息
get /brokers/topics/your_topic
# 查看当前 Controller
get /controller
7.2 关键节点速查表
| ls /brokers/ids | 查看集群所有 Broker |
| get /controller | 查看当前 Controller 是哪个 Broker |
| ls /brokers/topics | 查看所有 Topic |
| get /config/topics/topic名 | 查看 Topic 的配置覆盖 |
| ls /admin/reassign_partitions | 查看分区重分配任务 |
8. 总结:ZooKeeper 的功与过
ZooKeeper 的历史贡献:
- ✅ 为 Kafka 提供了成熟、稳定的分布式协调能力
- ✅ 降低了 Kafka 初期开发的复杂度
- ✅ 支撑了 Kafka 成为事实标准的消息队列
ZooKeeper 的局限:
- ❌ 不适合元数据高吞吐写入场景
- ❌ 大规模集群下性能瓶颈明显
- ❌ 双系统运维成本高
演进方向: Kafka 从 2.8 版本开始引入 KRaft 模式,3.3 版本生产可用,4.0 版本彻底移除 ZooKeeper 依赖。这是 Kafka 走向自包含、大规模、高性能的必经之路。
理解 ZooKeeper 在 Kafka 中的作用,不仅是为了运维旧版集群,更是为了深入理解 KRaft 的优化动机。
原创不易,欢迎点赞收藏 💡 评论区聊聊:你还在用 ZooKeeper 模式的 Kafka 吗?
标签:Kafka ZooKeeper 分布式协调 消息队列 架构设计

|
🌺The End🌺点点关注,收藏不迷路🌺 |





