第 12 / 19 篇

第 11 章 一份可以交付的小项目

项目目标与事件契约

现在你要完成一个“订单事件观察站”:订单生产者写入 orders;通知组和统计组独立读取;能观察分区和组进度;可以演示停止、恢复和回放。它不需要真的发送短信或扣款,终端输出代表下游收到事件。

事件的约定叫契约。项目采用以下字段:

字段 含义 示例
event_id 一次事件的唯一标识 e-101
order_id 业务订单号,同时作为 Key A2001
type 已发生的事实类型 OrderCreated
amount_fen 金额,整数分 5800
schema_version 数据格式版本 1

真实项目还应加入明确时区的事件发生时间、追踪标识等。不要随意把身份证号、银行卡号或密钥放进事件;历史回放会放大敏感数据传播范围。

运行项目

保持第 3 章 Broker 运行,并已按第 5 章创建 orders。启动两个新组:

bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 \
  --topic orders --from-beginning --group project-notify \
  --property print.key=true --property print.partition=true
bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 \
  --topic orders --from-beginning --group project-stats \
  --property print.key=true --property print.partition=true

生产者命令与第 5 章相同,依次输入:

A2001|{"event_id":"e-101","order_id":"A2001","type":"OrderCreated","amount_fen":5800,"schema_version":1}
A2002|{"event_id":"e-102","order_id":"A2002","type":"OrderCreated","amount_fen":3900,"schema_version":1}
A2001|{"event_id":"e-103","order_id":"A2001","type":"OrderPaid","amount_fen":5800,"schema_version":1}

两个终端都应该包含这三个事件 ID。同一订单的两个事件应落入同一分区。消费者还可能显示前几章的历史数据,这是预期现象,按事件 ID 判断本次结果。

做三次故障练习

第一,暂停通知消费者,统计消费者保持运行,再发送一条新订单。统计仍能读到;通知组恢复后,应能读取暂停期间仍然保留的事件。由于停机前的提交时机不同,恢复时也可能包含重复。

第二,为通知组启动第二个实例,使用 kafka-consumer-groups.sh --describe 观察分区分配。停止其中一个后,再观察接管。

第三,停止两个通知实例,按第 7 章的方法对 project-notify 做回放。统计组不需要随之回放,因为组进度彼此独立。

项目验收表

  • 能在主题描述中找到三个分区。
  • 两个不同组均能看到 e-101、e-102、e-103。
  • 同一个订单的创建与支付事件有相同分区编号。
  • 停止一个业务组不阻止另一个业务组继续读取。
  • 能解释重放为什么可能重复触发副作用。

这些命令验证了消息流动与组行为,没有实现真正的短信幂等和订单数据库事务。进阶时可选择一种熟悉的语言,把第 10 章的数据库去重流程做成小程序,再设计“业务成功后立即杀进程”的故障测试。