核心概念
这一课你将能做到
- 用自己的话解释 Broker、Topic、Partition、Offset、Consumer Group、副本(ISR),并指出「顺序保证」落在哪一层
- 画出「3 分区 Topic + 两组消费者」的 ASCII 图,标出谁在分摊、谁在完整重读
- 写出带
orderId 作 key 的 ProducerRecord,说明为什么同一订单的事件能保持分区内有序
为什么要学
后面写 Producer / Consumer / Spring Kafka 时,配置项全是这些名词。若把 Topic 当成「一个大队列」、把 Offset 当成「全局游标」,就会在 rebalance、重复消费、乱序上反复踩坑。
这些概念同时也是 Kafka 面试的地基题:面试官问「分区内有序怎么实现」「消费者数和分区数的关系」「offset 存在哪」,本质上都在验证你有没有把这张图画进脑子。本课先建立 追加日志 + 分区并行 + 组内位点 的图像;安装与代码课都建立在这张图上。
人话:Kafka = 很多本按页编号的日记本(分区),写的人按页追加,每本书可以复印多份(副本),读者用书签(offset)记下读到哪一页。
先建立图像
Cluster
└── Broker(Kafka 进程,可多台)
└── Topic: order-events ← 逻辑频道名
├── Partition 0: [m0][m1][m2]... offset 0,1,2...
├── Partition 1: [m0][m1]...
└── Partition 2: [m0]...
(每个分区在多 Broker 上有副本:1 Leader + N Follower)
Consumer Group "notify-svc"(负载均衡)
实例 A ← 读 P0, P1(组内互斥)
实例 B ← 读 P2
Consumer Group "audit-svc"(独立订阅)
实例 C ← 再读 P0+P1+P2 全部分区(自己独立的 offset)
| 概念 | 人话 | 记住一句 |
|---|
| Broker | 存分区数据的 Kafka 节点 | 进程/机器,不是 Topic 本身 |
| Topic | 逻辑频道,如 order-events | 名字空间,下面才分分区 |
| Partition | Topic 的物理分片 | 分区内有序;跨分区无全局序 |
| Offset | 分区内消息序号 | 每个「组 × 分区」一份进度 |
| Consumer Group | 同 group.id 的消费者集合 | 组内分摊;组间互不影响 |
| 副本/ISR | 同一分区在多 Broker 的拷贝 | Leader 写,Follower 追;跟得上的叫 ISR |
| Key | 生产者附带的键 | 同 key 进同一分区 → 保序 |
与本站其他组件的分工:
MySQL → 业务真相(订单行)
Redis → 热缓存 / 轻通知
Kafka → 「发生了什么」的可重放事件流
跟着做
步骤 1:把五个词写成「人话卡片」
在笔记上为 Broker / Topic / Partition / Offset / Consumer Group 各写一句人话(禁止只抄英文)。自检标准:能否不看表格,口头向同事解释「offset 是分区局部的,不是整个 Topic 一个号」。
步骤 2:在纸上复刻分区图
notify 组两个实例各管哪些分区
audit 组为什么还能再读同一 Topic
应看到:消息不会被「第一个组」删掉;第二组有自己的书签。
步骤 3:推演 key 与顺序
假设同一订单连续发三条:Created → Paid → Shipped,key 都是 orderId=1001。在笔记写:它们会进几个分区?消费时能否保证 Created 先于 Shipped?
答案方向:同 key → 哈希到同一分区 → 分区内追加有序;换 key 或无 key 则不保证订单级顺序。
步骤 4:算「几个消费者能真正干活」
题目:Topic 有 4 个分区,Consumer Group 启动 6 个实例。问:同时有几个实例分到分区?多出来的在干什么?
应看到:最多 4 个干活,多出的空闲等 rebalance;消费者数 > 分区数没有额外并行。反过来,4 分区只起 1 个实例,则一人扛四分区。
步骤 5:对照官方术语页
步骤 6:写一条带 key 的概念发送
import org.apache.kafka.clients.producer.ProducerRecord;
String topic = "coderoadmap.order-events";
String orderId = "1001"; // key → 分区路由
String json = "{\"event\":\"OrderCreated\",\"orderId\":\"1001\"}";
ProducerRecord<String, String> record =
new ProducerRecord<>(topic, orderId, json);
// 无 key 写法:new ProducerRecord<>(topic, json)
// → 消息轮询打散到各分区,丢订单级顺序
应看到:API 形状简单,但 key 的选择是业务顺序策略,不是装饰字段。
讲清楚要点
- 因为消息追加写在分区日志里,所以Kafka 的心智是「可回溯的日志」,不是「取走即删的队列」——这直接决定了多订阅者与重放能力。
- 因为顺序只在分区内保证,所以需要订单级顺序时必须稳定路由到同一分区(通常靠 key),跨分区无法承诺先后。
- 因为offset 按「消费者组 × 分区」记录(存在内部 Topic
__consumer_offsets),所以两组可独立重放;组内重启则从已提交位点继续,书签不存应用内存里。
- 因为组内一个分区同时只分配给一个消费者,所以水平扩展上限 ≈ 分区数;想加消费者先想分区数够不够。
- 因为副本解决的是「Broker 宕机数据不丢」,分区解决的是「并行与顺序」,所以两者是正交概念:分区是横向切,副本是纵向拷。
- 因为Broker 只持久化字节与元数据,所以schema 校验、业务幂等、字段演进都在应用层(后续消息格式/投递课展开)。
你可能会懵的地方
-
「Offset 是不是全局自增 id?」
不是。offset 每个分区从 0 起;比较消费进度必须带上 (topic, partition, offset) 三元组。说「我消费到 offset=100」而不说分区,等于没说。
-
「多加消费者一定更快?」
超过分区数后多余实例空转(只能等别人挂了顶上)。要提速先加分区(并评估 key 热点是否倾斜),再加实例。
-
「两个服务共用同一个 group.id 图省事?」
会变成竞争消费,审计可能收不全。规则一句话:一个职责一个 group.id。
-
「无 key 是不是更均匀、更好?」
均匀 ≠ 正确。需要实体有序时必须带稳定 key;如果担心大客户热点,可以业务上组合 key(如 shopId + orderId),而不是简单丢掉 key。
-
「副本和分区是一回事吗?」
分区是并行与顺序的边界;副本是同一分区数据在多 Broker 上的冗余。单节点实验 replication-factor=1,生产至少 2(常见 3)才扛得住宕机。
-
「Leader 是干嘛的?」
每个分区有一个 Leader 副本负责全部读写,Follower 只同步。ISR(In-Sync Replicas)是「跟得上 Leader」的副本集合,后面生产者课的 acks=all 等的就是 ISR 确认。
错法对照
| 错法 | 问题 | 正解 |
|---|
| 以为 Topic 内全局有序 | 顺序只承诺在分区内,跨分区并行无全局序 | 只承诺分区内有序;按 key 路由保实体序 |
所有事件 key=null 却要求同订单顺序消费 | 无 key 消息轮询散落多分区 | key=orderId(业务实体 id)固定分区 |
通知与审计共用一个 group.id | 组内互斥,一方收不全 | 两个 group.id,各自完整消费 |
| 用 Kafka 替代 MySQL 存订单最终状态 | 消息是事件流不是权威库 | MySQL 事务为准;Kafka 存可重放事件 |
| 6 个消费者配 3 个分区还指望 2 倍提速 | 超出分区数的消费者闲置 | 分区数 ≥ 期望并行消费者数 |
卡点急救
- 概念多到记不住:只抓三个动词——写进哪(Topic/Partition)、读到哪(Offset)、谁在读(Consumer Group),其余概念都能从这三个动词推出来。
- 画图分不清「谁在抢谁在读全量」:判断口诀——同
group.id 抢分区,不同 group.id 各读全量;画图前先给每个消费者标组名。
- 搞不清顺序该在哪层保证:需要「同一实体的操作有序」→ key 路由 + 分区内有序;需要「全局所有消息有序」→ Kafka 不擅长,只能单分区(牺牲吞吐)或应用层排序。
面试常问
-
Kafka 为什么吞吐量高?
背四点:①顺序写磁盘(追加日志,顺序 IO 接近内存速度);②页缓存(PageCache,读写都走 OS 缓存,不依赖 JVM 堆);③零拷贝(sendfile,数据不经过用户态);④批量发送 + 压缩(Producer 攒 batch,网络与存储都省)。
-
分区内有序是怎么保证的?如何让同一订单的消息有序?
分区日志只追加不修改,offset 单调递增,消费按 offset 顺序读——所以分区内天然有序。同一订单有序:Producer 用 orderId 作 key,默认分区器对 key 哈希固定路由到同一分区;注意消费端多线程乱处理仍可能乱序,需按 key 分发到单线程或内存队列。
-
分区数怎么定?越多越好吗?
起点公式:max(期望吞吐/单分区吞吐, 期望消费者并行数)。不是越多越好:每个分区对应更多文件句柄、更多副本同步、更大 rebalance 与控制器开销;未消费数据也可能摊薄 PageCache。先按当前需求留余量,后续可以增加(减少不行)。
自测三问
- Topic 4 分区、Group 2 实例:最多几个实例同时干活?升到 6 实例呢?为什么?
- Offset 与 MySQL 自增 id:相似点与不同点各写一条。
- 两个服务都要消费同一 Topic:应设几个
group.id?若误设成一个会怎样?
小结一句话
Kafka = 可分区的持久化日志 + 消费者组各自的位点进度;顺序在分区内,扩展上限看分区数,副本负责扛宕机。
延伸