Docker 运行 Kafka
这一课你将能做到
- 用 Docker Compose 拉起单节点 KRaft 模式 Broker(无 ZooKeeper),并用
localhost:9092 作为 bootstrap
- 创建 3 分区 Topic,用
kafka-topics --describe 核对分区数与副本因子
- 用
kafka-console-producer/consumer 完成「先发后收 / 从头消费」闭环,并写出 Spring 的连接配置
为什么要学
概念图再熟,没有 Broker 就无法验证分区、offset 与消费组行为。本地用 KRaft 单节点 最省事:新版 Kafka 已用内置 Raft 元数据管理替代 ZooKeeper,一个容器搞定,和你跑 Redis 的 Compose 体验一致。
本课目标是 能起、能建、能收发:搭好后,后面 Java 客户端、Spring Kafka、Streams 全部连这个 localhost:9092。生产要多 Broker、副本 ≥ 2、监听器安全(SASL/SSL),那些是上线话题,不阻碍今天动手。
人话:先在自己机器上开一扇 9092 的门,后面所有代码都敲这扇门。
先建立图像
你的电脑
docker compose
└── kafka 容器(KRaft:broker + controller 同进程)
监听 9092 ←── 宿主机 localhost:9092
监听 9093 ←── controller 内部用
分区日志存容器内(实验数据可丢)
终端 A: kafka-console-consumer (订阅 Topic)
终端 B: kafka-console-producer (追加记录)
Spring: spring.kafka.bootstrap-servers=localhost:9092
| 项 | 实验取值 | 生产直觉 |
|---|
| 模式 | KRaft 单节点 | 多 controller quorum / 多 broker |
replication-factor | 1 | ≥ 2(常见 3) |
| 分区数 | 3(方便看分摊) | 按吞吐与消费者数规划 |
| 数据 | 可随时 down -v 清空 | 挂卷、有备份与监控 |
跟着做
步骤 1:准备 Compose 文件
在实验目录新建 docker-compose.yml(KRaft 单节点,无 ZooKeeper):
services:
kafka:
image: apache/kafka:3.9.0
container_name: kafka-lab
ports:
- "9092:9092"
environment:
# 单节点同时扮演 broker 与 controller(KRaft)
KAFKA_NODE_ID: 1
KAFKA_PROCESS_ROLES: broker,controller
KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092,CONTROLLER://0.0.0.0:9093
# 广播给客户端的地址必须是宿主机能访问到的
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092
KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT
KAFKA_CONTROLLER_QUORUM_VOTERS: 1@localhost:9093
# 单节点必须把内部 topic 副本降为 1,否则启动/建 topic 会失败
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: 0
docker compose up -d
docker compose ps
docker logs kafka-lab --tail 20
应看到:容器 Up,日志里出现 Kafka Server started。PowerShell 下命令单行执行即可;9092 被占用时先停占用进程或改端口映射。
步骤 2:确认脚本可用
docker exec kafka-lab ls /opt/kafka/bin
应看到 kafka-topics.sh、kafka-console-producer.sh、kafka-console-consumer.sh、kafka-consumer-groups.sh 等。后续所有命令都走 docker exec kafka-lab ...。
步骤 3:创建 Topic 并 describe
docker exec kafka-lab /opt/kafka/bin/kafka-topics.sh --bootstrap-server localhost:9092 --create --topic coderoadmap.order-events --partitions 3 --replication-factor 1
docker exec kafka-lab /opt/kafka/bin/kafka-topics.sh --bootstrap-server localhost:9092 --describe --topic coderoadmap.order-events
应看到 PartitionCount: 3、ReplicationFactor: 1,三个分区 Leader 都在本节点。再建一个练习用 Topic:coderoadmap.audit-log,--partitions 2。
步骤 4:终端 A 启动消费者(从头读)
docker exec -it kafka-lab /opt/kafka/bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic coderoadmap.order-events --from-beginning
--from-beginning:这个新消费者组从最早消息读起(实验习惯)。窗口保持开着。
步骤 5:终端 B 生产消息
docker exec -it kafka-lab /opt/kafka/bin/kafka-console-producer.sh --bootstrap-server localhost:9092 --topic coderoadmap.order-events
{"event":"OrderCreated","orderId":"1001"}
{"event":"OrderCreated","orderId":"1002"}
应看到终端 A 立即出现相同内容。Ctrl+C 可退出任一终端。
步骤 6:验证「先发后启」与 Spring 连接串
- 停掉消费者,用 producer 再发 2 条,然后用同样的
--from-beginning 命令重新拉起——应能看到全部历史消息(日志还在,这就是 Kafka 与 Pub/Sub 的区别)。
- 写下 Spring 侧配置,后面课程直接沿用:
# application-dev.yml
spring:
kafka:
bootstrap-servers: localhost:9092
讲清楚要点
- 因为实验只需验证协议与工具,所以单节点 KRaft +
replication-factor=1 足够;把生产副本策略照搬到笔记本只会平白报错。
- 因为客户端靠
bootstrap-servers 找到集群再获取完整元数据,所以本机所有角色统一写 localhost:9092;容器内脚本连的是容器自己的 localhost:9092。
- 因为
advertised.listeners 告诉客户端「回头该连谁」,所以宿主机访问必须广播成 localhost:9092,否则 Java 客户端会拿到不可达地址一直超时。
- 因为KRaft 用 controller quorum 管理元数据,所以 Compose 里没有 ZooKeeper 服务;看到老教程里的 ZK 配置直接按 KRaft 改写。
- 因为日志默认按时间/大小保留,所以
--from-beginning 能读到历史;要跨 compose down 保留数据,需要挂 volume。
- 因为
__consumer_offsets 等内部 Topic 也受副本因子约束,所以单节点必须设 KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR=1 等,否则自动创建失败、消费组提交位点报错。
你可能会懵的地方
-
「还要不要 ZooKeeper?」
KRaft(Kafka Raft)自 2.8 引入、3.3 起生产可用,新版镜像不再需要 ZK。读文档认准 KRaft 配置段;面试被问「ZK 在 Kafka 里干什么」也能答出历史职责(Broker 注册、控制器选举)再补一句「已被 KRaft 取代」。
-
「容器内 localhost 和宿主机 localhost 是一回事吗?」
不是同一网络命名空间。docker exec 进容器时 localhost:9092 指容器内 Kafka;宿主机上的 Java 也用映射后的 localhost:9092;但另一个容器里连 Kafka 要写服务名(如 kafka:9092),不能写 localhost。
-
「create 报 replication 不足 / 内部 topic 建不出来」
单节点默认副本需求没降下来。显式 --replication-factor 1 建业务 Topic,并确认三个 *_REPLICATION_FACTOR / MIN_ISR 环境变量都在。
-
「consumer 什么都收不到」
新消费组默认可能从 latest 开始读,或 Topic 名打错。实验加 --from-beginning;用 kafka-topics.sh --list 核对名字。
错法对照
| 错法 | 问题 | 正解 |
|---|
--replication-factor 3 建在单节点上 | 副本数大于 broker 数必败 | 实验显式 --replication-factor 1 |
ADVERTISED_LISTENERS 写容器内部地址 | 宿主机客户端拿到不可达地址 | 广播成 PLAINTEXT://localhost:9092 |
| Topic 名每次手滑不一致 | 生产者/消费者连了两个 Topic | 固定 coderoadmap.order-events 贯穿全 track |
以为 compose down 后还能 --from-beginning 读旧消息 | 未挂卷,容器删除数据即丢 | 实验重灌即可;要持久化挂 volume |
| Compose 里加 ZooKeeper 服务 | 旧写法,镜像不支持联动 | 用 KRaft 环境变量,删 ZK 服务 |
卡点急救
- 容器起不来、日志报
Cluster ID mismatch:旧数据卷与新配置冲突,docker compose down -v 清掉重来(实验数据可丢)。
- Java 客户端报
Connection to node 1 could not be established:九成是 advertised listeners 问题。先 telnet localhost 9092 通不通,再核对广播地址是不是 localhost:9092。
- 控制台命令「找不到或无法加载主类」:镜像版本差异导致脚本路径不同,先
docker exec kafka-lab ls /opt/kafka/bin 确认真实路径再执行。
面试常问
-
KRaft 和 ZooKeeper 模式有什么区别?
老模式用 ZK 存 Broker 注册信息、做 Controller 选举;KRaft 把元数据放进 Kafka 自己的 Raft quorum(内部 Topic),Controller 与 Broker 合并可单进程运行。好处:少一个重型组件、元数据管理更简单、扩展性与恢复更快。新集群直接上 KRaft。
-
bootstrap-servers 和 advertised.listeners 的关系?
bootstrap 只负责第一次连接找入口;连接成功后 Broker 返回集群元数据,其中含每个 Broker 的 advertised 地址,后续读写按广告地址走。所以「能 ping 通 bootstrap 但客户端超时」几乎都是 advertised.listeners 配错。
自测三问
- 为什么单节点必须把
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR 设为 1?不设会看到什么现象?
- 先发 3 条再启动 console consumer,加
--from-beginning 能否全收到?不加呢?
- Spring 应用与
docker exec 脚本的 bootstrap-servers 各写什么?Java 报连不上时优先查哪两个配置?
小结一句话
会起 KRaft Broker、会建多分区 Topic、会用控制台收发与回放,安装即过关;Spring 只记 localhost:9092。
延伸