第 28 章 · Kafka
一句话:分布式的消息日志,生产者把消息追加到主题的分区里,消息按保留时间留在磁盘上,各消费者组自己记读到哪儿,读过的消息不删,可以重读。 用它还是用别的:订单、点击这类事件要给好几个服务各读一份,新服务上线要从头重读,或者量大到每秒上万条,用 Kafka;处理完就删的任务队列、按类型路由,用 RabbitMQ(第 27 章);已经有 Redis 且量不大,用 Redis Streams(第 26 章)。 核对版本:Kafka 4.0(随书 compose 镜像
apache/kafka:4.0.0),2026-10
28.1 安装与版本
Section titled “28.1 安装与版本”本书用 Docker 跑 Kafka,单节点、KRaft 模式。在随书的 compose/ 目录下启动,Compose 的用法详见第 13 章:
docker compose --profile mq up -d这一条同时起 RabbitMQ 和 Kafka。Kafka 有两个地址:宿主机上的程序连 localhost:9092,同一个 compose 里的其他容器连 kafka:19092。数据在命名卷 kafkadata 里。
命令行工具都在容器的 /opt/kafka/bin/ 下,前面加 docker compose exec kafka。在容器里用 localhost:9092 连的是同一个 broker 的宿主机监听器,所以本章命令一律写 --bootstrap-server localhost:9092。查版本,返回 4.0.0:
docker compose exec kafka /opt/kafka/bin/kafka-topics.sh --version不想用 Docker、要在本机解压运行的,见 28.7 的“快速开始”。
28.2 五分钟概念
Section titled “28.2 五分钟概念”- 主题与分区(topic、partition):主题是一类消息的名字,如
orders。一个主题分成几个分区,每个分区是一份只追加的日志,分区之间并行读写,只有同一分区内保证顺序。生产者带键(key)发送时,同键总进同一个分区;不带键时,生产者把一批消息写进同一个分区,攒满一批再换下一个分区,长期看分散到各分区,短时间内发的几条常落在同一个分区。 - 偏移量(offset):消息在分区里的序号,从 0 开始递增。消费者组提交偏移量,记下每个分区读到哪儿;重启后从提交处接着读,把偏移量改小就能重读。
- 消费者组(consumer group):一个分区同一时刻只分给组内一个成员,所以组内干活的成员最多和分区数一样多。不同的组互不影响,各读一份完整的消息。积压、提交偏移量的通用说法见第 26 章 26.2。
- 副本(replica):每个分区可以在几个 broker 上各存一份,一台挂了由别的副本接替;随书单节点只有一份,副本数是 1。
- 保留策略(retention):消息到期就删,默认留 7 天,与有没有被消费无关;也可以按大小删。
- KRaft:Kafka 用自己的 controller 管理集群元数据,不再需要 ZooKeeper,4.0 起已不支持 ZooKeeper 模式。随书 compose 里同一个进程既是 broker 又是 controller。
下图是一个三分区的主题:同键的消息总在同一个分区;组 g1 有两个成员,分区分给它们;组 g2 只有一个成员,三个分区都归它。
28.3 常用命令
Section titled “28.3 常用命令”脚本在容器里,表中省略前缀 docker compose exec kafka /opt/kafka/bin/。
| 任务 | 命令 | 说明 |
|---|---|---|
| 主题 | ||
| 建主题 | kafka-topics.sh --bootstrap-server localhost:9092 --create --topic orders --partitions 3 |
不写 --partitions 用 num.partitions,默认 1 |
| 列出主题 | kafka-topics.sh --bootstrap-server localhost:9092 --list |
__ 开头的是内部主题 |
| 看分区和副本 | kafka-topics.sh --bootstrap-server localhost:9092 --describe --topic orders |
每个分区一行,含 Leader、Replicas、Isr |
| 加分区 | kafka-topics.sh --bootstrap-server localhost:9092 --alter --topic orders --partitions 6 |
只能加不能减;加了之后同一个键可能换到别的分区 |
| 收发 | ||
| 发消息 | kafka-console-producer.sh --bootstrap-server localhost:9092 --topic orders |
每输入一行发一条,Ctrl+C 退出 |
| 带键发 | kafka-console-producer.sh --bootstrap-server localhost:9092 --topic orders --property parse.key=true --property key.separator=: |
输入 u1:paid,冒号前是键 |
| 从头收 | kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic orders --from-beginning |
不加 --from-beginning 只收启动之后的新消息 |
| 以组的身份收 | kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic orders --group g1 |
自动提交偏移量,下次接着读 |
| 消费者组 | ||
| 列出组 | kafka-consumer-groups.sh --bootstrap-server localhost:9092 --list |
已经没有成员在线的组也列出 |
| 看积压 | kafka-consumer-groups.sh --bootstrap-server localhost:9092 --describe --group g1 |
LAG 是 LOG-END-OFFSET 减 CURRENT-OFFSET |
| 重置偏移量 | kafka-consumer-groups.sh --bootstrap-server localhost:9092 --group g1 --topic orders --reset-offsets --to-earliest --execute |
组内消费者要先全部停掉;换成 --dry-run 只预览 |
| 保留时间 | ||
| 改一个主题的保留时间 | kafka-configs.sh --bootstrap-server localhost:9092 --alter --entity-type topics --entity-name orders --add-config retention.ms=86400000 |
留 1 天,覆盖 broker 的 log.retention.hours |
| 看主题改过的配置 | kafka-configs.sh --bootstrap-server localhost:9092 --describe --entity-type topics --entity-name orders |
只列出单独设过的项 |
28.4 典型工作流
Section titled “28.4 典型工作流”以下命令都省略前缀 docker compose exec kafka /opt/kafka/bin/,收发要两个终端。
建主题、发、收一遍
Section titled “建主题、发、收一遍”- 建一个三分区的主题:
kafka-topics.sh --bootstrap-server localhost:9092 --create --topic orders --partitions 3。 - 看详情,
PartitionCount: 3、ReplicationFactor: 1,三个分区的 Leader 都是 1:kafka-topics.sh --bootstrap-server localhost:9092 --describe --topic orders。 - 在终端一打开生产者,在
>提示符后依次输入1001、1002、1003,每行一条:kafka-console-producer.sh --bootstrap-server localhost:9092 --topic orders。 - 在终端二从头收,三条都出现,不带键时几条可能落在不同分区,顺序不一定是输入的顺序:
kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic orders --from-beginning。 - 在终端一再输入
1004,终端二马上收到;退出消费者,提示Processed a total of 4 messages:Ctrl+C。
看积压、重置偏移量重新消费
Section titled “看积压、重置偏移量重新消费”接着用上面的 orders:
- 以组
g1读一遍,新组第一次读也要加--from-beginning,收完按Ctrl+C:kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic orders --group g1 --from-beginning。 - 在生产者里再发
1005、1006、1007三条:kafka-console-producer.sh --bootstrap-server localhost:9092 --topic orders。 - 看积压,各分区
LAG加起来是 3,第一行提示Consumer group 'g1' has no active members.:kafka-consumer-groups.sh --bootstrap-server localhost:9092 --describe --group g1。 - 预览把偏移量拨回最早,
NEW-OFFSET列全是 0:kafka-consumer-groups.sh --bootstrap-server localhost:9092 --group g1 --topic orders --reset-offsets --to-earliest --dry-run。 - 执行重置,组内有消费者在线会报错,要先停掉:
kafka-consumer-groups.sh --bootstrap-server localhost:9092 --group g1 --topic orders --reset-offsets --to-earliest --execute。 - 再看,
CURRENT-OFFSET都是 0,LAG等于全部 7 条:kafka-consumer-groups.sh --bootstrap-server localhost:9092 --describe --group g1。 - 以
g1再读,不加--from-beginning也从第一条开始,7 条全部重来:kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic orders --group g1。
只重放最近一段,把 --to-earliest 换成 --shift-by -2(每个分区退 2 条)或 --to-datetime 2026-10-08T00:00:00.000。
按键分区,让同一用户的消息有序
Section titled “按键分区,让同一用户的消息有序”- 建主题:
kafka-topics.sh --bootstrap-server localhost:9092 --create --topic user-events --partitions 3。 - 打开带键的生产者,冒号前是键:
kafka-console-producer.sh --bootstrap-server localhost:9092 --topic user-events --property parse.key=true --property key.separator=:。 - 依次输入
u1:login、u2:login、u1:pay、u3:login、u1:logout,输完退出;某一行漏了冒号,生产者报No key separator found并退出:Ctrl+C。 - 收的时候显示分区和键,
u1的三条都在同一个Partition:后面,且按 login、pay、logout 的顺序:kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic user-events --from-beginning --property print.partition=true --property print.key=true。 - 只读
u1所在的那个分区(把 1 换成上一步看到的号):kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic user-events --partition 1 --from-beginning。
程序里也一样:发送时把用户 ID 或订单号作为键,不要自己指定分区号。
28.5 配置要点
Section titled “28.5 配置要点”broker 的配置文件是 server.properties,一行一个 键=值。位置与附录 C 一致:
| 配置项 | 路径 | 说明 |
|---|---|---|
| 配置文件(解压安装) | config/server.properties |
在 Kafka 解压目录下,改完重启 broker |
| Docker 镜像内 | /etc/kafka/docker/ |
镜像自带的单节点默认配置;传了 KAFKA_ 环境变量就不再用它,所以随书 compose 把必需的项都写全了 |
随书 compose 不挂配置文件,用 kafka 服务 environment: 里的环境变量设。配置项名换成环境变量的规则:. 换成 _,原有的 _ 换成 __,- 换成 ___,全部大写,前面加 KAFKA_。对照如下:
| 配置项 | 随书 compose 的环境变量 | 当前值与说明 |
|---|---|---|
log.retention.hours |
未设,要改就加 KAFKA_LOG_RETENTION_HOURS |
默认 168(7 天) |
num.partitions |
未设,要改就加 KAFKA_NUM_PARTITIONS |
默认 1;建主题不写 --partitions 和自动建主题时用 |
listeners |
KAFKA_LISTENERS |
PLAINTEXT://:19092,HOST://:9092,CONTROLLER://:9093,在容器里监听的端口 |
advertised.listeners |
KAFKA_ADVERTISED_LISTENERS |
PLAINTEXT://kafka:19092,HOST://localhost:9092,告诉客户端以后连哪个地址 |
log.dirs |
KAFKA_LOG_DIRS |
/var/lib/kafka/data,挂在卷 kafkadata 上;不设时镜像用 /tmp/kraft-combined-logs |
客户端第一次连上 broker 后,broker 回给它 advertised.listeners 里对应监听器的地址,之后的收发都连这个地址。所以监听器要分两个:宿主机和容器看到的 broker 地址不一样。
改了环境变量,再执行一次 docker compose --profile mq up -d 会重建容器,数据在卷里不丢。对应的 server.properties 写法如下,前三项与随书 compose 相同,后三项是示例值,随书 compose 未设,# 开头的行是注释:
# 两个客户端监听器加一个 controller 监听器,在容器里监听的端口listeners=PLAINTEXT://:19092,HOST://:9092,CONTROLLER://:9093# 公布给客户端的地址:容器之间用 kafka:19092,宿主机用 localhost:9092advertised.listeners=PLAINTEXT://kafka:19092,HOST://localhost:9092# 数据目录,随书 compose 挂在卷 kafkadata 上log.dirs=/var/lib/kafka/data# 不写分区数时建 3 个分区num.partitions=3# 消息留 3 天,与有没有被消费无关log.retention.hours=72# 单个分区最多留 1GB,与时间限制谁先到按谁删log.retention.bytes=107374182428.6 注意事项
Section titled “28.6 注意事项”- 日志反复报
Connection to node 1 (localhost/127.0.0.1:9092) could not be established。 原因:容器里的程序连了kafka:9092,broker 回的地址是localhost:9092,指向它自己。处理:容器连kafka:19092,宿主机连localhost:9092。 - 消费者启动后什么也收不到,之前发的消息看不见。 原因:新消费者组默认
auto.offset.reset=latest,只从最新处开始读。处理:命令行加--from-beginning,程序设auto.offset.reset=earliest;已提交过偏移量的组用--reset-offsets拨回。 - 同一组起了 4 个消费者,只有 3 个在处理消息。 原因:主题只有 3 个分区,一个分区只分给组内一个成员,多出的闲着。处理:
--describe --members查各成员分到几个分区;按 28.3 的“加分区”加到 6 个。 - 同一用户的“已退出”先于“已登录”被处理。 原因:没带键或键不一致,几条消息落到了不同分区,各分区并行读。处理:用用户 ID 作键;加分区会改变键到分区的映射,加完之后的一段时间顺序可能乱。
- 磁盘被占满,broker 报
No space left on device后退出。 原因:默认保留 7 天,与是否消费无关,写入量大时卷被撑满。处理:用kafka-configs.sh给大主题设retention.ms,或在 compose 里加KAFKA_LOG_RETENTION_HOURS、KAFKA_LOG_RETENTION_BYTES。
28.7 深入阅读
Section titled “28.7 深入阅读”- Kafka 4.0 文档:官方文档首页,设计、配置、运维、安全、KRaft 都从这里进。
- 快速开始:下载解压或用 Docker 起一个单节点,建主题、发、收消息的最短路径;本机不用 Docker 时照这里装。
- apache/kafka Docker 镜像:官方镜像的标签、单节点启动示例,以及用 KAFKA_ 环境变量覆盖配置的规则。
- broker 配置参考:server.properties 全部配置项的含义、类型和默认值,log.retention.hours、num.partitions、advertised.listeners、log.dirs 都在这里。