Spring Boot Kafka 项目 Demo:订单事件系统 专家知识、源码阅读路线与面试题

Demo 目标

项目目录:

text 复制代码
kafka-knowledge-system/demos/springboot-kafka-order-demo

这个 demo 模拟"订单服务发送订单事件,消费者处理并保存处理结果"的完整链路,覆盖生产项目最常见能力:

  • REST API 接收订单。
  • Producer 发送 Kafka 消息。
  • 使用 orderId 作为 key 保证同订单顺序。
  • Consumer 手动确认 offset。
  • 消费失败自动重试。
  • 重试失败后进入 DLT。
  • 消费端基于 eventId 做幂等。
  • 提供查询接口验证处理结果。

项目结构

text 复制代码
springboot-kafka-order-demo
├── pom.xml
├── src/main/java/com/example/kafkaorder
│   ├── KafkaOrderDemoApplication.java
│   ├── config/KafkaConfig.java
│   ├── controller/OrderController.java
│   ├── event/OrderEvent.java
│   ├── producer/OrderEventProducer.java
│   ├── consumer/OrderEventConsumer.java
│   └── service/OrderEventStore.java
└── src/main/resources/application.yml

启动 Kafka

先启动 CLI demo 中的 Kafka:

bash 复制代码
cd kafka-knowledge-system/demos/cli-kafka-lab
docker compose up -d

确认端口:

bash 复制代码
docker compose ps

Kafka 对宿主机暴露 localhost:9094,Spring Boot demo 默认连接这个地址。

启动项目

bash 复制代码
cd kafka-knowledge-system/demos/springboot-kafka-order-demo
mvn spring-boot:run

如果首次运行 Maven 需要下载依赖,请保持网络可用。

发送正常订单事件

bash 复制代码
curl -X POST http://localhost:8080/orders \
  -H 'Content-Type: application/json' \
  -d '{"orderId":"O1001","userId":"U1","amount":99.80,"status":"CREATED"}'

预期返回:

json 复制代码
{
  "eventId": "....",
  "eventType": "ORDER_CREATED",
  "orderId": "O1001",
  "userId": "U1",
  "amount": 99.80,
  "status": "CREATED"
}

查询处理结果:

bash 复制代码
curl http://localhost:8080/orders/O1001

预期能看到订单事件已被消费者处理。

触发失败和 DLT

发送一个状态为 FAIL 的事件:

bash 复制代码
curl -X POST http://localhost:8080/orders \
  -H 'Content-Type: application/json' \
  -d '{"orderId":"O9001","userId":"U9","amount":10.00,"status":"FAIL"}'

消费者会模拟业务异常,重试后进入 DLT:

bash 复制代码
docker compose exec kafka kafka-console-consumer \
  --bootstrap-server localhost:9092 \
  --topic order-events.DLT \
  --from-beginning \
  --timeout-ms 5000

核心代码解释

Producer

OrderEventProducer 发送消息时使用:

java 复制代码
kafkaTemplate.send("order-events", event.orderId(), event)

这里的 key 是 orderId,作用是让同一个订单的事件进入同一个 partition,保证单订单维度顺序。

Consumer

OrderEventConsumer 使用手动确认:

java 复制代码
store.handle(event);
acknowledgment.acknowledge();

只有业务处理成功后才提交 offset。失败时抛出异常,交给 Spring Kafka 的错误处理器重试。

幂等

OrderEventStore 使用 eventId 记录已处理事件。如果同一事件重复投递,消费者直接跳过,避免重复执行业务逻辑。

生产环境可以把这个逻辑落到数据库唯一键:

sql 复制代码
create table processed_event (
  event_id varchar(64) primary key,
  processed_at timestamp not null
);

处理流程:

text 复制代码
开始数据库事务
插入 processed_event(event_id)
如果唯一键冲突,说明已处理,直接返回
执行业务更新
提交事务
提交 Kafka offset

验证清单

验证项 操作 预期
Topic 自动创建 启动应用 order-events 存在
Producer 正常 POST /orders 返回事件 JSON
Consumer 正常 GET /orders/{id} 能查到处理结果
Key 分区 多发同一 orderId 同订单顺序处理
重试 status=FAIL 日志出现重试
DLT 消费最终失败 order-events.DLT 有消息
幂等 重复 eventId 不重复处理

项目扩展练习

  1. 增加订单支付事件 ORDER_PAID
  2. 把内存存储替换为 MySQL。
  3. 使用 Outbox 表保证数据库订单和 Kafka 事件最终一致。
  4. 增加消息 Schema 版本字段兼容处理。
  5. 增加 Prometheus 指标:发送成功数、消费成功数、DLT 数、处理耗时。
  6. 增加 Testcontainers 集成测试。

专家级能力模型

Kafka 专家不是只会背 ISR、HW、ACK,而是能把业务目标、客户端配置、集群状态、监控指标和故障现场串起来。

能力 具体表现
架构设计 能设计 topic、partition、副本、Schema、权限和容量
可靠性治理 能解释丢失、重复、乱序,并给出业务幂等方案
性能治理 能从 producer、broker、consumer 三端定位瓶颈
运维治理 能扩容、迁移、升级、故障演练
排障能力 能通过 lag、ISR、请求延迟、磁盘指标定位问题
源码理解 能理解 Controller、Coordinator、Replica、Log、Network

源码阅读路线

Kafka 源码可以按链路阅读,不建议从入口文件盲读。

第一阶段:请求链路

目标:理解 producer 请求到 broker 后怎么处理。

重点模块:

  • SocketServer
  • KafkaRequestHandler
  • KafkaApis
  • ReplicaManager
  • Partition
  • UnifiedLog

阅读问题:

  1. ProduceRequest 如何进入 broker?
  2. broker 如何找到 topic partition leader?
  3. 消息如何追加到日志?
  4. 什么时候返回成功?

第二阶段:复制链路

重点模块:

  • ReplicaFetcherThread
  • ReplicaManager
  • Partition
  • ISR 管理逻辑

阅读问题:

  1. follower 如何从 leader 拉取数据?
  2. ISR 什么时候扩大或缩小?
  3. HW 如何推进?
  4. leader 宕机后如何保证消费者不读到不稳定数据?

第三阶段:消费组

重点模块:

  • GroupCoordinator
  • ConsumerCoordinator
  • AbstractCoordinator
  • assignor 实现

阅读问题:

  1. 消费者如何加入 group?
  2. rebalance 如何触发?
  3. offset 存在哪里?
  4. static membership 如何减少 rebalance?

第四阶段:Controller 和元数据

重点模块:

  • KRaft Controller
  • metadata log
  • partition leader election

阅读问题:

  1. KRaft 如何替代 ZooKeeper?
  2. controller 负责哪些元数据变更?
  3. leader election 如何写入元数据日志?
  4. broker 如何感知元数据变化?

完整面试题与答案

1. Kafka 为什么吞吐高?

Kafka 高吞吐来自多个设计叠加:磁盘顺序追加写、OS page cache、批量发送、压缩、零拷贝、partition 并行、broker 横向扩展。顺序写避免随机 IO;page cache 让热点数据读写走内存;producer 将多条消息合批降低网络请求数;消费者批量拉取;partition 分布在多个 broker 上提高并行度。

追问:Kafka 是不是完全不依赖磁盘?

不是。Kafka 数据最终存储在磁盘日志中,只是大量读写会被 page cache 吸收。磁盘性能仍然决定长期稳定吞吐和恢复速度。

2. Topic、Partition、Offset 的关系是什么?

Topic 是消息分类,partition 是 topic 的物理分片,offset 是消息在某个 partition 内的递增位置。Kafka 只保证同一个 partition 内消息有序,不保证 topic 全局有序。消费者提交的是某个消费组在某个 topic-partition 上的 offset。

3. 为什么同一消费组内,一个 partition 不能被多个消费者同时消费?

因为 Kafka 用 partition 作为并行和顺序边界。如果同一个 partition 同时分给多个消费者,就无法保证 offset 提交和顺序处理的一致性。同一消费组内可以多个 partition 分给多个消费者,但一个 partition 同一时刻只分配给一个消费者。

4. Kafka 如何保证消息不丢?

需要三端配合:

  • Producer:acks=allenable.idempotence=true、合理重试。
  • Broker:核心 topic 使用 3 副本、min.insync.replicas=2、禁用 unclean leader election。
  • Consumer:业务处理成功后再提交 offset。

还要接受一个现实:业务端通常需要幂等,因为网络超时和重试会造成重复投递。

5. Kafka 会不会重复消费?

会。Kafka 常见语义是至少一次投递。比如消费者业务处理成功后,提交 offset 前宕机,重启后会重新消费同一条消息。解决方式是在业务层用 eventId、业务唯一键、去重表或状态机做幂等。

6. 如何保证消息顺序?

把需要保证顺序的消息用同一个 key 发送到同一个 partition。例如同一订单的事件用 orderId 作为 key。Kafka 保证 partition 内顺序。消费者处理时也要避免对同一 partition 内消息并发乱序处理。

边界:如果需要全局顺序,只能使用一个 partition,但吞吐和并行度会下降。

7. ISR 是什么?

ISR 是 In-Sync Replicas,表示与 leader 保持同步的副本集合。Producer 使用 acks=all 时,leader 需要等待 ISR 中满足条件的副本确认后才返回成功。ISR 缩小说明副本复制跟不上,可能由 broker 宕机、网络抖动、磁盘慢或 GC 引起。

8. HW 和 LEO 的区别是什么?

LEO 是某个副本日志末尾 offset,代表该副本写到了哪里。HW 是消费者可见的最高安全 offset,代表 ISR 副本都确认到的位置。消费者只能读取 HW 之前的数据,避免读到 leader 写入但尚未复制、后来可能丢失的数据。

9. acks=all 是否一定不丢消息?

不是。acks=all 需要配合副本和 ISR 配置才有意义。如果 topic 只有一个副本,acks=all 也只是等一个副本。生产环境应使用 replication.factor=3min.insync.replicas=2,并禁用 unclean leader election。

10. 什么是 unclean leader election?

当 ISR 中没有可用副本时,Kafka 是否允许一个落后副本成为 leader。允许时可提高可用性,但可能丢失已确认消息;禁止时宁可 partition 暂时不可用,也不让落后副本成为 leader。核心业务通常禁用。

11. Consumer Lag 很高怎么排查?

先判断 lag 是持续增长还是短暂波峰。持续增长时按顺序排查:消费者是否报错、处理耗时是否增加、下游是否慢、消费者实例数是否不足、partition 是否不足、是否频繁 rebalance、是否某个 partition 热点。临时方案是扩容消费者、降低批处理耗时、启用 DLT、暂停非核心消费。

12. Rebalance 频繁的原因是什么?

常见原因:消费者处理时间超过 max.poll.interval.ms、实例频繁发布或崩溃、心跳超时、topic partition 变化、消费组成员不稳定。治理方式:优化处理耗时、合理调大 max.poll.interval.ms、使用 cooperative sticky assignor、静态成员 ID、优雅停机。

13. Kafka 事务解决什么问题?

Kafka 事务解决 Kafka 内部多条消息写入和 offset 提交的原子性。例如从 topic A 消费,处理后写 topic B,同时提交 A 的 offset,要么都成功,要么都不可见。它不能直接保证数据库和 Kafka 的跨系统强一致。

14. 数据库更新成功但 Kafka 发送失败怎么办?

推荐 Outbox 模式。业务事务中同时写业务表和 outbox_event 表。事务提交后,由后台任务或 CDC 读取 outbox 表发送 Kafka。发送成功后标记状态。这样避免"数据库成功但消息丢失"的不一致。

15. Kafka 如何做容量规划?

先收集峰值 TPS、平均消息大小、保留时间、副本数、压缩比、目标磁盘利用率。存储公式:

text 复制代码
TPS * 消息大小 * 86400 * 保留天数 * 副本数 * 压缩比 / 磁盘目标利用率

再根据目标吞吐和消费者并行度估算 partition 数,并预留增长空间。

16. Partition 可以随便增加吗?

可以增加,但不能减少。增加 partition 会改变 key 到 partition 的映射,可能影响按 key 的顺序假设。增加过多还会增加文件句柄、元数据、controller、rebalance 和恢复成本。

17. Kafka 和 RabbitMQ 怎么选?

Kafka 更适合高吞吐、事件流、可回放、多订阅、日志采集和流计算。RabbitMQ 更适合传统任务队列、复杂路由、低到中等吞吐、每条消息投递语义更强的场景。选型要看业务是否需要保留事件流和回放能力。

18. 如何设计 Kafka 消费端幂等?

常见方式:

  • 事件表唯一键:event_id 作为主键。
  • 业务状态机:订单已支付就不重复支付。
  • Redis set 去重:适合短期去重,但要处理过期和一致性。
  • 数据库唯一约束:最稳妥,适合核心业务。

核心原则:先判断是否处理过,再执行业务更新,并把判断和业务更新放在同一个事务边界内。

19. 如何处理毒丸消息?

毒丸消息是永远无法成功处理的消息,例如字段格式错误、业务状态非法。不能无限重试阻塞 partition。应该有限次重试,失败后进入 DLT,同时记录原因、topic、partition、offset、key、异常堆栈,后续人工修复或补偿。

20. Kafka 专家排障时最先看什么?

先看影响面:是 producer 写不进、consumer 堆积、broker 不健康,还是某个 topic 异常。然后看四组指标:consumer lag、under replicated partitions、request latency、磁盘/网络/CPU。不要只看应用日志,Kafka 问题通常需要客户端、broker 和下游系统一起看。

相关推荐
空中海2 小时前
Kafka 基础:从消息队列到事件流平台
分布式·kafka·linq
空中海4 小时前
Kafka Streams、Connect 与生态
分布式·kafka·linq
默 语20 小时前
基于 Spring Boot 3 + LangChain4j 快速构建企业级 AI 应用实战
人工智能·spring boot·后端
薪火铺子21 小时前
SpringBoot WebServer启动与监听器原理深度解析
spring boot·后端·tomcat
KmSH8umpK21 小时前
SpringBoot 分布式锁实战:从单机锁到Redis分布式锁全覆盖,解决超卖、重复下单、幂等并发问题
spring boot·redis·分布式
jay神1 天前
基于团队模式的C程序设计课程辅助教学管理系统
java·spring boot·vue·web开发·管理系统
长河1 天前
基于 Jib 实现无 Dockerfile 的 Spring Boot 应用容器化
java·spring boot·后端
Arya_aa1 天前
一:病虫害 AI 识别系统项目初期准备与Docker初识,VM虚拟机
spring boot
敖正炀1 天前
Spring MVC 启动全景:DispatcherServlet 与父子容器
spring boot