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综合练习

消息格式

JSON vs Avro、Schema Registry 概念。

30 分钟

消息格式

这一课你将能做到

  1. 用 JSON 定义 OrderCreated 事件信封,经 KafkaTemplate 发送并用 ObjectMapper 反序列化消费
  2. 对照表格说清「新增可选字段 / 删字段 / 改类型 / 改字段名」对兼容性的影响,并给出安全的演进顺序
  3. 说清实验阶段为何优先 JSON,以及 Avro + Schema Registry 解决什么问题(概念级)

为什么要学

Kafka Broker 只存字节:不帮你校验字段、不帮你做版本迁移。团队如果各写各的 JSON,消费方迟早翻车在「缺字段 / 类型变了 / 同名异义」上——这类事故在日志里只是一行 JsonProcessingException,排查半天才发现是上游偷偷改了字段类型。

消息格式就是 Topic 的公共 API:生产者与消费者的契约。本站 REST 用 Jackson + JSON,事件流先沿用同一套最顺手;等跨语言、多团队共享 Topic、需要自动兼容检查时,再上 Avro + Schema Registry。

人话:Topic 是管道,契约在消息格式。管道不会替你检查内容,契约坏了下游全坏。

先建立图像

Producer                         Broker                      Consumer
  POJO/Map ──序列化──► bytes ──► 分区日志 ──► bytes ──反序列化──► POJO
                │                                      │
                └── 契约:字段名、类型、兼容规则 ──────┘

Avro + Registry(概念图):
  Producer 注册 schema → 消息头带 schema id → [magic][id][avro bytes]
  Consumer 按 id 向 Registry 取 schema → 反序列化
  → 兼容性在注册时强制检查
方案优点代价何时用
JSON 字符串可读、易调试、与 REST 同栈无强 schema、体积大实验 / 小团队
JSON Schema可校验治理靠自觉中等规模
Avro + Registry紧凑、多语言、强制兼容检查要运维 Registry多团队事件总线

跟着做

步骤 1:定一条事件契约(纸上)

写下最小字段,规则:event(类型)+ eventId(幂等键)+ 业务主键 + 发生时间:

{
  "event": "OrderCreated",
  "eventId": "01JBEXAMPLEUUID",
  "orderId": "1001",
  "userId": "u42",
  "amount": 9900,
  "occurredAt": "2026-10-06T00:00:00Z"
}

约定写进笔记:payload 带够用的只读快照(金额、用户),避免消费方事事回查生产方数据库;写路径冲突仍以 MySQL 事务为准。

步骤 2:Java record + 发送

public record OrderCreatedEvent(
        String event,
        String eventId,
        String orderId,
        String userId,
        long amount,
        Instant occurredAt
) {}

// 发送侧:对象 → JSON 字符串(实验序列化策略)
OrderCreatedEvent evt = new OrderCreatedEvent(
        "OrderCreated",
        UUID.randomUUID().toString(),
        "1001", "u42", 9900L, Instant.now());
String json = objectMapper.writeValueAsString(evt);
// key=orderId → 同订单分区内有序(生产者课的纪律)
kafkaTemplate.send("coderoadmap.order-events", evt.orderId(), json);

用 console consumer 核对:Topic 里是可读 JSON,key 是订单号。

步骤 3:消费反序列化

@KafkaListener(topics = "coderoadmap.order-events", groupId = "coderoadmap-notify")
public void onMessage(String payload) throws Exception {
    OrderCreatedEvent evt = objectMapper.readValue(payload, OrderCreatedEvent.class);
    if (!"OrderCreated".equals(evt.event())) {
        return; // 或按类型分发到不同 handler
    }
    notifyService.handle(evt); // 内部按 eventId 幂等
}

应看到:字段一一映射。故意做一次实验:生产者先发一条缺 amount 的消息,观察 Jackson 行为——record 构造器缺参会抛异常。这说明反序列化失败要进死信/告警(上一课的 DefaultErrorHandler),不能静默吞掉后照常 ack。

步骤 4:演一出「兼容」小实验

  1. 生产者新增可选字段 "note":"hello",消费者的 record 暂不声明。
  2. 给 ObjectMapper 配 FAIL_ON_UNKNOWN_PROPERTIES=false(Boot 默认已关),老消费者照常工作。
  3. 笔记写结论:新增可选字段 ≈ 安全;删字段或改类型要先升级消费者再升级生产者。
# Boot 默认行为(可显式声明):
spring.jackson.deserialization.fail-on-unknown-properties: false

步骤 5:设计 TrackUpdated 事件

为本站课程域写一版:

{
  "event": "TrackUpdated",
  "eventId": "01JC...",
  "trackId": "kafka-essentials",
  "slug": "kafka-essentials",
  "title": "Kafka 精讲",
  "version": 2,
  "occurredAt": "2026-10-06T01:00:00Z"
}

回答两个问题:哪个字段做消费幂等键(eventId,全局唯一不复用)?version 服务什么(实体级乐观并发/投影排序)?

步骤 6:扫一眼 Avro 流程(不必部署)

在笔记画:

Producer → Registry 注册 schema(id=42) → Broker: [magic][42][avro-bytes]
Consumer → 用 42 拉 schema → 反序列化
Registry 拒绝不兼容 schema 注册(BACKWARD 兼容模式)

本课不强制起 Registry 服务;理解「把契约检查从事故现场挪到注册时」即可。

讲清楚要点

  1. 因为Broker 不解释业务字段,所以格式与兼容规则是团队契约,要写进文档并过评审——改字段 ≈ 改接口。
  2. 因为JSON 人类可读,所以实验与排错成本低;kafka-console-consumer 直接能看,事故现场肉眼可查。
  3. 因为消费方可能滞后部署,所以演进铁律:只加不改不删——新增可选字段安全;删字段、改类型、改字段名都是破坏性变更,要走双写/版本过渡。
  4. 因为只传 id 会强迫每个消费者回查生产方库,所以事件带只读快照字段;但快照是「发生时」的值,权威修正仍回 MySQL。
  5. 因为eventId 稳定唯一,所以它同时服务幂等(去重表键)与追踪(日志 grep 一个 id 贯穿全链路)。
  6. 因为类型是契约的一部分,所以金额用 long(分)不用浮点、时间用 ISO-8601/epoch millis 不用本地化字符串——这些坑比「加字段」更常见。

你可能会懵的地方

  1. 「Kafka 能帮我校验 JSON Schema 吗?」 默认不能,Broker 只看字节。校验发生在生产者(序列化前)、消费者(反序列化时)或 Schema Registry(Avro/Protobuf 注册时)。

  2. 「新增字段老消费者一定挂?」 不是。消费方配置忽略未知属性(Boot 默认)时,新增可选字段通常安全;危险的是删字段、改类型、改语义(同名字段含义变了)。

  3. 「Avro 是不是必须学?」 单团队、Java 栈、JSON 够用;多团队共享 Topic、跨语言、要自动兼容检查时才上。面试答出「Registry 解决的是契约治理问题」即可加分。

  4. 「和 Redis 里存 JSON 有什么不同?」 存储的都是字节,但职责不同:Redis 的 JSON 是缓存/状态(可随时重算覆盖),Kafka 的 JSON 是历史事件流(不可变,用于重放与审计)。缓存丢了重算,事件丢了要靠对账补。

  5. 「事件里要不要放全量订单数据?」 不放全量,放消费方够用的快照。全量导致事件与表结构强耦合,表加列事件就得跟着改;够用原则 + 需要时消费方回查/订阅后续事件。

错法对照

错法问题正解
各服务随便拼字段,无 event/eventId消费方无法分发、无法幂等统一信封:类型 + 幂等键 + 时间
消息只传 orderId,人人查库拼数据与生产方库强耦合、放大查询压力带只读快照;库作补偿与修正
直接改字段类型上线(amount: long→string)老消费者反序列化全挂新增字段过渡,双写期后下线旧字段
反序列化失败吞异常还继续 ack毒丸消息静默丢失不 ack 进重试,重试耗尽投死信并告警
金额用 double 存进事件精度丢失,对账对不上long 存分 / BigDecimal+字符串

卡点急救

  1. 消费报 UnrecognizedPropertyException:消费方开启了未知字段校验。统一 Boot 默认(忽略未知字段),并把它写进团队规范,而不是每个服务各配各的。
  2. 上游加了字段下游没感知就上线:契约评审流程 + 消费端用「宽松反序列化 + 必填字段断言」双保险;必填字段缺失要显式报错进死信,而不是拿 null 继续算。
  3. 同一条消息两个消费方解析结果不一致:多半是两端 Jackson 版本/配置漂移。把 record 类抽到共享 event-contract 模块(或代码生成),契约只有一个来源。

面试常问

  • 为什么消息要有统一格式/信封? 三个理由:①分发——event 字段让一个 Topic 可以承载多类事件并按类型路由;②幂等——eventId 是去重表唯一键,重复投递可安全跳过;③排障——occurredAt + eventId 能把生产、Broker、消费三段日志串成一条时间线。没有信封的消息只能当不透明字符串处理。

  • Schema 演进如何做到不破坏消费者? 原则「只加不改不删」:新增字段给默认值且可空;废弃字段先标记 deprecated 停止写入、观察期后再删;类型不变、语义不变。用 Avro + Registry 时选 BACKWARD 兼容模式,注册即校验;纯 JSON 时靠评审 + 宽松反序列化 + 必填断言兜底。

  • JSON 和 Avro 怎么选? JSON:可读、调试方便、生态零成本,适合小团队/实验;Avro:二进制紧凑(省存储与带宽)、强 schema、Registry 强制兼容检查、多语言代码生成,适合大规模事件总线。中间态也可以 JSON + 团队规范,等痛了再迁。

自测三问

  1. 为 TrackUpdated 列出 JSON 字段,并指出哪个做消费幂等键、为什么不是 version。
  2. 新增可空 note 字段时老消费者为何仍工作?直接把 amount 从 long 改成 string 会发生什么?
  3. Redis 存 JSON 与 Kafka 消息 JSON:各举一个本站更合适的场景。

小结一句话

先 JSON 统一信封(event + eventId + 快照 + 时间)并守住「只加不改不删」;规模与多语言上来再上 Avro + Schema Registry。

延伸

  • Kafka 官方文档 — 序列化配置
  • 下一课「投递语义」:at-least-once / exactly-once 到底指什么,幂等与 Outbox 如何落地。
加载本节练习…
← 上一课Spring Kafka下一课 →投递语义