CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
Kafka 消息队列入门/课时/综合练习
RawBlame
课时目录12 节

Kafka 消息队列入门

  1. 01消息队列是什么
  2. 02核心概念
  3. 03Docker 运行 Kafka
  4. 04生产者
  5. 05消费者组
  6. 06Spring Kafka
  7. 07消息格式
  8. 08投递语义
  9. 09Kafka Streams 入门
  10. 10监控与 lag
  11. 11Kafka vs RabbitMQ
  12. 12综合练习

综合练习

订单事件 Topic + Spring 消费端。

40 分钟

综合练习

这一课你将能做到

  1. 独立搭起「API 写 MySQL → 发 OrderCreated → 双 Consumer Group 各自处理」的完整 lab 链路
  2. 为通知与审计两条消费链路实现幂等(去重表/唯一索引),并用 CLI 验证 lag 回落
  3. 用 README 写清本站边界(生产主路径 MySQL+Redis,Kafka 为实验),并逐项完成验收清单

为什么要学

前面 11 课每个都是零件:概念、安装、生产、消费、Spring、格式、投递、Streams、监控、选型。零件各自能跑 ≠ 系统能跑——真实工作里你交付的从来不是「会用某个 API」,而是一条能演示、能抗重启、能说清边界的链路。

本课做一个结营作业:完整的订单事件实验。做完它,你就有了能写进简历的实战叙述:「我用 Spring Boot + Kafka 搭过订单事件链路,双消费组订阅、去重表幂等、CLI 验证过 lag」——这句话的每个词你都亲手敲过。

不要求接入生产 API;独立 lab 表 + 本地 Kafka 即可,大胆试错。

人话:结营作业——能跑通、能讲清、能写边界,才算入门闭环。

先建立图像

POST /lab/orders
  └─ @Transactional 写 lab_orders(权威)
  └─ afterCommit → KafkaTemplate.send
        Topic: coderoadmap.order-events(3 分区,key=orderId)
             │
     ┌───────┴────────┐
     ▼                ▼
 group notify     group audit
 记通知日志        INSERT lab_audit_log
 (幂等)          (UNIQUE event_id)
     │                │
     └────────┬───────┘
              ▼
 kafka-consumer-groups --describe → 两组 LAG ≈ 0
元素值
Topiccoderoadmap.order-events
事件OrderCreated(必做)、OrderPaid(选做)
KeyorderId
消费组coderoadmap-notify、coderoadmap-audit
权威库lab 自己的 lab_orders 表(非生产 cf_*)

跟着做

步骤 1:起 Broker 与 Topic

docker compose up -d
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。

步骤 2:建表

-- 订单真相(lab)
CREATE TABLE lab_orders (
  id VARCHAR(64) PRIMARY KEY,
  user_id VARCHAR(64) NOT NULL,
  amount BIGINT NOT NULL,          -- 金额存分,不用浮点(消息格式课的纪律)
  status VARCHAR(32) NOT NULL,
  created_at TIMESTAMP NOT NULL
);

-- 审计投影:event_id 唯一索引 = 幂等闸门
CREATE TABLE lab_audit_log (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  event_id VARCHAR(64) NOT NULL UNIQUE,
  order_id VARCHAR(64) NOT NULL,
  payload JSON NOT NULL,
  created_at TIMESTAMP NOT NULL
);

-- 通知侧去重表
CREATE TABLE lab_processed_events (
  event_id VARCHAR(64) PRIMARY KEY,
  processor VARCHAR(32) NOT NULL,
  processed_at TIMESTAMP NOT NULL
);

步骤 3:API + 事务后发送

@RestController
@RequiredArgsConstructor
public class LabOrderController {
    private final LabOrderService orderService;
    private final OrderEventPublisher publisher;
    private final ObjectMapper objectMapper;

    @PostMapping("/lab/orders")
    public LabOrder create(@RequestBody CreateLabOrderCmd cmd) throws Exception {
        LabOrder order = orderService.create(cmd); // @Transactional 写 lab_orders
        OrderCreatedEvent evt = OrderCreatedEvent.from(order);
        publisher.publishCreated(evt.orderId(), objectMapper.writeValueAsString(evt));
        return order;
    }
}

// 事务后发送(生产者课 + Spring 课的合并纪律)
@Service
@RequiredArgsConstructor
public class OrderEventPublisher {
    private final KafkaTemplate<String, String> kafkaTemplate;

    public void publishCreated(String orderId, String json) {
        kafkaTemplate.send("coderoadmap.order-events", orderId, json)
                .whenComplete((r, ex) -> {
                    if (ex != null) { /* 记日志/告警,进补偿 */ }
                });
    }
}

LabOrderService.create 内部用 TransactionSynchronizationManager.registerSynchronization 的 afterCommit 调 publisher,或直接在 Controller 层「service 事务方法返回后再发」——两种都行,但必须写注释说明选的是哪种、风险窗口是什么。

步骤 4:双监听 + 各自幂等

@Component
@RequiredArgsConstructor
public class LabOrderListeners {
    private final NotifyFacade notifyFacade;
    private final AuditFacade auditFacade;
    private final ObjectMapper objectMapper;

    @KafkaListener(topics = "coderoadmap.order-events",
                   groupId = "coderoadmap-notify", concurrency = "3")
    public void notify(String payload) throws Exception {
        OrderCreatedEvent evt = objectMapper.readValue(payload, OrderCreatedEvent.class);
        notifyFacade.onOrderCreated(evt); // 内部查 lab_processed_events,命中即跳过
    }

    @KafkaListener(topics = "coderoadmap.order-events",
                   groupId = "coderoadmap-audit")
    public void audit(String payload) throws Exception {
        OrderCreatedEvent evt = objectMapper.readValue(payload, OrderCreatedEvent.class);
        auditFacade.onOrderCreated(evt); // INSERT lab_audit_log,撞 UNIQUE(event_id) 则忽略
    }
}

两个 groupId 是硬性要求:验证「多订阅者各自全量」这一核心模型。审计侧 onOrderCreated 用 INSERT IGNORE 或捕获 DuplicateKeyException 静默跳过,都算幂等。

步骤 5:验收操作

  1. 连续 POST /lab/orders 5 笔。
  2. 查库:lab_orders 5 行;lab_audit_log 5 行;通知日志 5 次(三处都对上)。
  3. 重启 notify 应用(或重启整个 Spring Boot),对同一批消息重新投递(可用 kafka-consumer-groups --reset-offsets --to-datetime 或重发同 eventId 的消息):通知不应重复执行——日志应出现「skip eventId=...」。
  4. 验证同订单事件有序:同一 orderId 再发 OrderPaid,确认消费者日志里 Created 恒在 Paid 之前、且两者 partition 相同。
  5. CLI 看两组 lag:
docker exec kafka-lab /opt/kafka/bin/kafka-consumer-groups.sh --bootstrap-server localhost:9092 --describe --group coderoadmap-notify
docker exec kafka-lab /opt/kafka/bin/kafka-consumer-groups.sh --bootstrap-server localhost:9092 --describe --group coderoadmap-audit

LAG 应趋近 0。

步骤 6:写 README 边界(必做)

用自己的话写三节:

  1. 何时 Redis 删缓存 vs 发 Kafka 事件:举本站各一例(课程缓存失效广播 / 订单多下游处理)
  2. 本 lab 与生产主站的关系:独立 /lab/** 与 lab 表,不碰 cf_* 权威路径
  3. 已知简化与下一步:如未上 Outbox(事务后直接发送的崩溃窗口)、未配死信 Topic、未接监控

这三节是交付物的一部分,不是空话——面试官问「你这个项目的边界和风险」,答案就在这里。

讲清楚要点

  1. 因为双 group 才是多订阅者模型,所以验收必须看到 notify 与 audit 各自 5 条,而不是两组抢 5 条——这是本 lab 最核心的验证点。
  2. 因为at-least-once 必然可能重复,所以「重启后不重复副作用」要靠幂等证明(skip 日志),不能靠运气。
  3. 因为key=orderId,所以同一订单的 Created/Paid 同分区有序;换任何其他 key 策略前先想清楚顺序需求。
  4. 因为LAG 反映追赶情况,所以验收清单包含 describe,而不仅是「日志有了」——位点与业务进度是两回事。
  5. 因为实验库与生产库物理隔离,所以可以放心制造积压、杀进程、清 Topic,把故障场景都演一遍。
  6. 因为主站可以完全无 Kafka 运行,所以 README 里的边界句是工程判断力的证明:知道什么时候该用什么,比什么都用上更难。

你可能会懵的地方

  1. 「两个 Listener 能共用一个 groupId 吗?」 不能(会互抢分区,各自收不全)。本练习硬性要求两个 groupId;这正是概念课「组内互斥、组间全量」的实操。

  2. 「审计 5 行但通知只有 3?」 分组排查:--describe 看两组各自的 LAG 与成员;看应用错误日志(大概率是反序列化或 DB 异常被吞);用 console consumer 确认 Topic 内确有 5 条,把问题定位到消费侧还是生产侧。

  3. 「幂等了为什么还要关心位点提交?」 位点不前进会反复 poll 同一段消息,浪费资源且 lag 永远不归零。幂等是安全网,位点是进度,两者都要正确。

  4. 「必须用 Streams 吗?」 不要求。本练习聚焦生产/消费/幂等/lag 四件事;Streams 是独立的进阶方向,适合作为课后扩展。

  5. 「事件丢了怎么发现?」 对账视角:定时任务比对「lab_orders 行数」与「lab_audit_log 行数」(或 outbox 缺口)。本 lab 可手查;生产靠对账任务 + 告警——这也是 afterCommit 方案风险窗口的标准兜底。

错法对照

错法问题正解
notify/audit 共用 groupId组内互斥,互相抢消息收不全两个 group,各收全量
无 eventId,靠「别重启」防重复重启/rebalance 立刻露馅去重表/唯一索引兜底
只验日志,不验 LAG 与表行数「看起来在跑」≠ 数据一致验收五项逐条勾
把 lab 直接接到生产订单 API实验故障污染真实业务独立 /lab/** 路由与 lab 表
事务内随手 send回滚后下游处理幽灵订单afterCommit 或 Outbox,注释写明风险

卡点急救

  1. 监听器一条消息都没收到:按链路查——Topic 存在?(--list)发送回调是否报错?groupId 拼写?组是否已停在 latest(换新组试)?console consumer 先确认消息真的进了 Topic。
  2. 重启后重复副作用:检查幂等是否真的生效——去重表有没有插上(SELECT * FROM lab_processed_events)、唯一索引建了没有、检查与副作用是否在同一事务/同一方法内。
  3. LAG 居高不下:回看监控课三板斧——处理耗时(是不是 sleep 忘删了)、实例数 vs 分区数、下游(lab 的 DB 写入)是否被拖慢;逐层定位而不是重启碰运气。

面试常问

  • 介绍一下你做过的 Kafka 项目(用本 lab 回答) 背结构:背景(订单创建后要通知、审计,多下游解耦)→ 方案(Topic 3 分区、key=orderId 保序、双消费组订阅)→ 难点(at-least-once 重复 → eventId 去重表幂等;事务边界 → afterCommit 发送)→ 验证(重启幂等验证、CLI 看 lag、分组 describe)→ 边界(未上 Outbox 的风险窗口与对账兜底)。这个五段式适用于任何项目介绍。

  • 如果消息量涨 10 倍,你的链路哪里先崩? 背思路而不是背答案:①先看消费并行度——3 分区 3 并发已满,加实例无效,需扩分区;②再看待性——DB 写入是瓶颈则审计表分库分表或换存储;③再看保留与磁盘——retention 与容量规划;④最后看生产端——afterCommit 同步发送会拖慢 API,考虑异步化/批量。能答「先崩在哪」说明真的理解链路。

自测三问

  1. 验收清单五项能否全部现场演示?缺哪项补哪项。
  2. README:何时 Redis 删缓存 vs 发 Kafka 事件?各举本站一例。
  3. 回顾本 track 12 课,用三句话串起「日志 → 组 → 幂等」这条主线。

验收清单

  • Compose 起 Broker,Topic 3 分区
  • 发 5 笔订单,notify 与 audit 两组各处理 5 次
  • 重启 notify,幂等防重复副作用(有 skip 日志)
  • --describe 两组 LAG 趋近 0
  • README 注明:主站仍 MySQL + Redis,Kafka 仅实验,已知简化写清

小结一句话

订单 Topic + 双消费组 + 幂等 + 会看 lag = Kafka 入门闭环;生产权威仍在 MySQL/REST,Kafka 是你事件驱动能力的扩展起点。

延伸

  • Apache Kafka 官方文档
  • 下一步方向:把 lab 能力带进真实项目——给 Spring Boot 主站加一条真实事件链路(如课程更新通知搜索索引),或继续进阶 Spring Cloud 事件总线与生产部署(多 Broker、SASL/SSL、Prometheus 监控)。学完本 track,你已经具备读懂任何 Kafka 生产架构图的底子。
加载本节练习…
← 上一课Kafka vs RabbitMQ