这本书的骨架
卷一回答一个问题:一条消息从 send() 调用到落进日志文件,中间发生了什么,为什么有时候不按你以为的方式发生。
为了回答这个问题,全书分成五个部分,前三部分是「理解」,后两部分是「使用与维护」:
| 部分 | 章节 | 主题 | 读完能做什么 |
|---|---|---|---|
| 第一部分 | 第 1–3 章 | 消息系统的形状、实验底座、生产者 | 说清楚分区、顺序、交付语义的关系 |
| 第二部分 | 第 4–6 章 | 副本、选举、KRaft、位移与消费组协议 | 解释 ISR 收缩、leader 切换、再平衡停顿 |
| 第三部分 | 第 7–9 章 | 生产者与消费者源码、序列化与指标 | 从源码里找到某个行为的原因 |
| 第四部分 | 第 10–12 章 | 存储、网络与复制、压测与容量 | 做容量估算,知道压测数字可信到什么程度 |
| 第五部分 | 第 13–15 章 | 运维动作、故障图谱、监控与验收 | 处理集群故障,给出可执行的处理顺序 |
前两部分是概念密度最高的地方。第三部分是源码,读起来慢但一次性收益最大。第四、五部分可以当手册查。
每一章回答的那个问题
写目录的时候,最没用的是只列标题。所以这里给每章配一个「它到底在回答什么」:
| 章 | 章节 | 它回答的问题 |
|---|---|---|
| 1 | 第 1 章 消息系统的形状 | 消息系统有几种形态?Kafka 为什么是日志? |
| 2 | 第 2 章 实验底座与物理结构 | 数据到底存在哪几个文件里? |
| 3 | 第 3 章 生产者 | 我调的这几个参数,各自在改什么? |
| 4 | 第 4 章 副本与选举 | 一台机器挂了,我的数据还在不在? |
| 5 | 第 5 章 KRaft | 没有 ZooKeeper 之后,集群靠什么协调? |
| 6 | 第 6 章 位移与消费组协议 | 消费者重启之后,为什么从奇怪的位置开始读? |
| 7 | 第 7 章 生产者内部 | send() 之后,消息去了哪个缓冲区? |
| 8 | 第 8 章 消费者内部 | poll() 一次,背后发了几个请求? |
| 9 | 第 9 章 序列化与扩展点 | 我能在客户端插什么,不能插什么? |
| 10 | 第 10 章 存储 | 为什么 Kafka 号称顺序写很快?边界在哪? |
| 11 | 第 11 章 网络与复制 | 分区数加上去,为什么反而慢了? |
| 12 | 第 12 章 压测与容量 | 我的压测数字能信吗?容量怎么估? |
| 13 | 第 13 章 运维动作 | 重启、扩容、换盘,正确的顺序是什么? |
| 14 | 第 14 章 故障 | 面板上这个指标红了,下一步查什么? |
| 15 | 第 15 章 监控与验收 | 哪些指标必须告警?验收怎么算通过? |
如果你手上正有一个具体问题,直接看最后一列,比从头读快。
第一部分:消息系统与实验底座
这一部分建立两件事:消息系统的直觉,以及一套能反复用的实验环境。
| 节 | 标题 | 一句话 |
|---|---|---|
| 1.1 | 把消息系统看成日志 | 消费不删数据,位移是读者自己的进度 |
| 1.2 | 分区:顺序与并行度的交换 | 分区既是并行单位,也是顺序单位 |
| 1.3 | 交付语义的真实成本 | 三档语义各要付出什么 |
| 2.1 | 三个节点的最小集群 | listener 与 advertised.listener 为什么要分开 |
| 2.2 | Topic 的物理结构 | 段文件、索引,以及一次读请求的寻址 |
| 2.3 | 保留与清理 | delete 与 compact 的语义差别 |
| 3.1 | 批次、linger 与压缩 | 三个参数互相挤压 |
| 3.2 | acks、重试与幂等 | 失败窗口在哪里,幂等挡住了什么 |
| 3.3 | 分区器与键设计 | 默认分区器的粘性行为,与热点 key |
第 1 章不需要环境,读完就能建立直觉。第 2 章开始需要容器,所以 2.1 同时承担「实验环境搭建」的职责——如果这一步跑不通,后面的实验都做不了。
第 3 章讲生产者,是全书第一次动手写客户端代码。它的前置只有 1.2(分区)和 1.3(语义),不依赖服务端机制。
第二部分:服务端机制
这一部分进入 broker 内部。它不需要写太多客户端代码,但需要三类实验:单节点、三节点、以及「杀掉一个节点」的破坏性操作。
| 节 | 标题 | 一句话 |
|---|---|---|
| 4.1 | 副本、LEO 与高水位 | 消费者只能读到 HW 之前 |
| 4.2 | ISR 收缩与 min.insync.replicas | 两个参数联合决定能不能写 |
| 4.3 | Leader 选举与不干净选举 | 优先副本、受控关闭、ELR |
| 5.1 | KRaft:控制器与元数据日志 | 元数据本身也是一条日志 |
| 5.2 | 集群成员与元数据传播 | 变更多久能被客户端看见 |
| 5.3 | 客户端的元数据刷新 | Metadata 状态机与过期抖动 |
| 6.1 | 位移与 __consumer_offsets | 位移存在哪,和消费位置差在哪 |
| 6.2 | 经典再平衡协议 | JoinGroup/SyncGroup 与停顿 |
| 6.3 | KIP-848 新一代协议 | 服务端主导分配,迁移要注意什么 |
第 4 章是三节点实验的核心。4.1 建立 LEO/HW 的关系,4.2 用「杀一个 broker 还能写、杀两个就不能写」把这个关系变成可观测的事实,4.3 讲选举策略。
第 5 章讲 KRaft。这里有一个版本陷阱:4.x 已经彻底移除 ZooKeeper,所有命令用 --bootstrap-server,--zookeeper 参数不存在。如果你在别处看到「用 --zookeeper 连集群」,那是 3.x 的写法。
第 6 章讲消费者协调。6.2 和 6.3 是同一件事的两代实现,读的时候要分清「classic 协议的行为」和「新协议的行为」,两者在再平衡停顿上的表现不同。
第三部分:客户端源码
这一部分最慢,也最值。它要求读者愿意打开 sources jar,跟着方法调用读下去。
| 节 | 标题 | 一句话 |
|---|---|---|
| 7.1 | KafkaProducer 的线程模型 | 调用线程 + 一个 Sender 线程 |
| 7.2 | RecordAccumulator 与批次缓存 | 每个分区一个队列,共享一个内存池 |
| 7.3 | Sender 与 NetworkClient | 请求怎么拆分,in-flight 怎么限制 |
| 8.1 | KafkaConsumer 的两套实现 | 门面背后是 Classic 还是 Async |
| 8.2 | Fetcher 与拉取管线 | 4.x 重构之后,Fetcher 变小了 |
| 8.3 | ConsumerCoordinator 与位移提交 | 自动提交的确切时机 |
| 9.1 | 序列化器与 Schema 边界 | 消息里不要放可变对象 |
| 9.2 | 拦截器链与埋点 | 能改什么,不能改什么 |
| 9.3 | 客户端指标口径 | 77 项指标里哪几个有用 |
这一部分有一个反复出现的提醒:不要凭 3.x 的印象写 4.x 的源码。消费者的实现、Fetcher 的职责、group.protocol 的分支,在 4.x 都变了。每一节都会给出 grep 命令和类文件大小,读者可以自己复核。
第四部分:存储与性能
这一部分回答「为什么快」和「快到哪里为止」。
| 节 | 标题 | 一句话 |
|---|---|---|
| 10.1 | 顺序写、页缓存与零拷贝 | 快的是顺序追加和页缓存,不是魔法 |
| 10.2 | 段滚动、索引重建与恢复 | 崩溃之后索引怎么回来 |
| 10.3 | 磁盘与文件描述符规划 | 容量、fd、IO 线程三笔账 |
| 11.1 | 网络线程与请求队列 | network 与 io 线程的分工 |
| 11.2 | 副本同步的吞吐与延迟 | follower 追赶的代价 |
| 11.3 | 分区数的非线性成本 | 上千分区为什么是坏主意 |
| 12.1 | 官方压测工具与方法论 | 两个 perf-test 脚本,以及它们的局限 |
| 12.2 | 吞吐、延迟与持久性的三角 | 三个指标不能同时最好 |
| 12.3 | 容量规划 | 从 QPS 推分区数与保留期 |
第 12 章会明确写:本机的压测数字只能做相对比较,不能做容量结论。这不是谦虚,是这台机器 load 常年在十几到二十几之间波动决定的。
第五部分:运维与故障
这一部分最接近日常工作。它的组织方式不是「知识点」,是「动作」。
| 节 | 标题 | 一句话 |
|---|---|---|
| 13.1 | 集群拓扑与机架感知 | broker.rack 与跨 AZ 流量 |
| 13.2 | 滚动重启与最小可用 | 重启顺序决定可用性 |
| 13.3 | 副本重建与限流 | 追赶太猛会把正常流量挤掉 |
| 14.1 | 故障图谱 | URP、离线分区、磁盘满、fd 耗尽 |
| 14.2 | 积压的分层定位 | 从 lag 一路查到根因 |
| 14.3 | 数据不一致的取证 | 副本之间怎么比对 |
| 15.1 | 监控指标与告警 | 12 个必看指标与阈值 |
| 15.2 | 版本升级与兼容性 | 升级顺序与协议兼容 |
| 15.3 | libraryd-mq 卷一验收 | 全卷实验的汇总与判据 |
附录
| 篇 | 标题 | 什么时候翻 |
|---|---|---|
| A | Kafka 命令速查 | 正在敲命令,想确认参数 |
| B | 集群与客户端模板 | 要从零搭一套环境 |
附录 A 里的端口、参数都是本机实际用过的,可以直接复制。附录 B 的 compose 文件就是本书实验用的那一份。
三条阅读路径
路径一:顺序通读(约 30 小时)
1.1 → 1.2 → 1.3 → 2.1 → …… → 15.3。适合想建立完整机制理解的人。第 1–3 章大约 6 小时,第 4–6 章 8 小时,第 7–9 章源码 8 小时,第 10–15 章 8 小时。
路径二:机制跳读(约 12 小时)
只读第 1、4、6、7、8 章。这五章覆盖三个最高频的疑问:为什么消费者会重复、为什么再平衡会停顿、为什么分区数不能乱加。读完可以跳过第 2、3、5 章的细节,需要时再回查。
路径三:值班查表(按需)
附录 A 的命令 + 第 14 章的故障图谱 + 15.1 的指标阈值。这条路径不追求理解,只追求「现在该敲什么命令」。
每节末尾的「判据」是什么
本书每一节结尾附近都会有一个小段落,标题通常是「本节判据」或「怎么判断」。它回答的不是「讲完了吗」,而是「什么现象出现,说明你真的理解了」。
举三个例子:
- 1.2 节的判据是:你能否解释「为什么 5 条消息按顺序发出,消费者看到的却是
book-3在前」。 - 4.2 节的判据是:看完
NOT_ENOUGH_REPLICAS之后,你是否能说出「此时再减一个副本会怎样」。 - 8.3 节的判据是:你能否指出自动提交在哪个时间点可能丢掉一批已处理的记录。
这个设计来自一个观察:技术书读完之后最常说的一句话是「好像懂了」。判据的作用是把它变成一个可以自己回答的问题。答不出来,说明这一节需要重读或重跑实验。
和其它书的关系
这本卷属于《Java 消息中间件》三卷本的第一卷。三卷的关系是:
| 卷 | 什么时候读 |
|---|---|
| 卷一(本书) | 想理解机制,或者在排查「为什么行为和我以为的不一样」 |
| 卷二《可靠消息与消费治理》 | 要设计不丢不重的链路,或者正在处理积压与重复 |
| 卷三《事件驱动架构》 | 要决定「这个业务该不该用事件」,或者设计 CQRS 与流处理 |
此外,下面几本书和本卷有交叉,但角度不同:
- 《Java 并发编程·性能诊断与事故》 :讲线程、锁、GC 与容器配额。本卷讲的是跨进程的消息链路,那本讲的是单进程内的并发。读第 11、12 章时如果对 CPU 与内存的观测手段不熟,可以对照那本的诊断工具章节。
- 《Java 网络编程基础》 :讲 Socket、NIO、HTTP/2 与 TLS。Kafka 的协议建立在 TCP 之上,第 11.1 节讲网络线程时会假设你了解基本的 TCP 语义。
- 《Java 虚拟机原理与性能调优》 :讲 GC 与内存布局。Kafka 客户端大量使用堆外内存与页缓存,第 10 章会引用那本的概念。
读之前需要的前置知识
这本书不假设你已经是 Kafka 专家,但假设你具备三样东西:
| 前置 | 为什么需要 | 不熟怎么办 |
|---|---|---|
| Java 语言基础 | 第三部分要读源码,看懂泛型、lambda、CompletableFuture | 先读《Java 编程入门》的泛型与并发章节 |
| 基本的命令行能力 | 几乎所有实验都用 docker exec 进容器敲脚本 | 附录 A 给了完整的命令形式,照抄即可 |
| 容量单位与简单估算 | 第 12 章要做分区数与磁盘的乘法 | 这一节会把每一步算式写出来 |
不需要的前置:不需要用过 ZooKeeper(4.x 已经没有它),不需要装过 Kafka(第 2.1 节从零开始),不需要懂 Scala(本书只读 clients 模块,纯 Java)。
有一个前置不建议带进来:其它版本 Kafka 的既有印象。3.x 到 4.x 的变化足够大,尤其是消费者实现和 KRaft 的默认化。带着旧印象读,容易在「我记得应该是……」的地方卡住。每节遇到这类变化时会明确标出「4.x 与 3.x 的差异」。
4.x 与旧教材的七处不同
如果你读过 2.x 或 3.x 时代的 Kafka 材料,下面这几处会让你在实验里直接卡住。本书在对应章节会逐条展开,这里先给一张速查表:
| 旧印象 | 4.x 的实际 | 本书位置 |
|---|---|---|
| Kafka 需要 ZooKeeper | 4.0 起只支持 KRaft,ZooKeeper 已移除 | 5.1 |
--zookeeper 是合法参数 | 所有命令都用 --bootstrap-server | 附录 A |
broker.id 是必填项 | 改用 node.id,broker 角色由 process.roles 决定 | 2.1 |
消费者就是 Fetcher 那一套 | 新增 AsyncKafkaConsumer(KIP-848),两套实现并存 | 8.1 |
| 再平衡一定「停世界」 | cooperative-sticky 与 KIP-848 都能减少停顿 | 6.2 / 6.3 |
--producer-property 是标准用法 | 已废弃,改用 --command-property,实测会打 deprecation warning | 3.1 |
--describe 输出只有 Leader/Replicas/Isr | 4.x 多了 Elr 与 LastKnownElr 两列 | 4.3 |
这张表不是让你背,是让你在遇到「命令报错」时先来这里对一眼。本书所有实验都是在 4.3.0 上跑的,凡是涉及行为变化的地方都会显式标注。
哪些实验可以跳过
不是每一节都需要动手。下面这张表说明每个实验的性质,方便你按时间预算取舍:
| 实验类型 | 涉及章节 | 需要的环境 | 可以只看结论吗 |
|---|---|---|---|
| 纯命令验证 | 1.1、2.2、2.3、6.1、附录 A | 单节点 | 可以 |
| 客户端代码 | 3.1–3.3、7–9、12 | 单节点 + Maven | 建议跑一遍,代码是理解的关键 |
| 破坏性操作 | 4.2、4.3、13.2、14.1 | 三节点 | 不可以,破坏性实验的结论依赖你亲眼看到状态变化 |
| 性能与压测 | 10.1、11.2、12.1 | 三节点 | 可以只看相对关系,绝对值本就不可信 |
| 运维流程 | 13.1、13.3、15.2 | 三节点 | 可以只看流程,但要知道每一步的判据 |
「破坏性操作」那一行的措辞是刻意的。读别人写的「杀掉 leader 之后 ISR 从 3 变成 2」,和自己在终端里看着 --describe 的输出从 Isr: 2,3,1 变成 Isr: 3,1,理解深度完全不同。这一类实验,本书建议一定自己跑。
一个使用建议
这本书的每一节都按同一个结构写:先给一个具体问题,再给机制,再给实验,最后给判据。
如果你时间有限,只读每节开头的「问题」和结尾的「判据」,中间跳过实验,也能得到一个可用的结论。但如果你要拿这个结论去做生产决策,请至少把实验跑一遍——这本书里没有一个数字是编的,其中不少也和「常识」相反。
另一个建议是:遇到与自己经验冲突的段落,先别急着判它错。本书有若干结论初看反直觉,比如「send() 成功不代表消费者能读到」「acks=all 仍然可能丢」「分区数加到某个点之后整体吞吐反而下降」。这些都不是笔误,而是机制决定的。每一处都给了可复现的命令,请先跑,再下结论。
如果跑出来的结果和书里不一样,那也可能不是书错了,而是你的版本、参数或机器状态不同。把这三种差异记录下来,本身就是一次很好的学习。
从第 1.1 节 开始。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。