2. Kafka 架构与消息流:Producer、Broker、Consumer、Topic、Partition、Offset 核心概念

要排查消息丢失,你得先搞清楚 Kafka 到底是怎么把消息从 A 搬到 B 的。说白了,Kafka 就是一个分布式的消息系统,核心就三块:生产者、服务端、消费者。我见过不少同事,上来就调参数,结果连消息在哪个环节丢的都没搞明白。嗯,咱们先把这几个核心角色理清楚。

2.1 整体架构:一条消息的旅程

一条消息从产生到被消费,会经过下面这些节点。我习惯把这个流程叫做「消息的生命周期」。

核心流程: Producer → Broker (Topic → Partition) → Consumer

Kafka 消息流核心架构图 Producer 生产者 Broker 集群 Topic: orders Partition-0 | Partition-1 | Partition-2 Consumer 消费者 发送消息 拉取消息 Offset:消息在 Partition 中的位置 每个 Partition 是一个有序的日志文件

2.2 Producer:消息的起点

Producer 就是发送消息的一方。你想想看,如果 Producer 把消息发出去,但 Broker 没收到,那消息就丢了。这是最常见的丢失场景之一。

Producer 的工作流程大致是这样的:

  1. 序列化:把对象转成字节数组。
  2. 分区选择:决定这条消息去哪个 Partition。
  3. 发送:把数据发往 Broker。

我的经验: 我曾经遇到过一个线上问题,Producer 端设置了 acks=0,结果 Broker 挂了,消息直接丢了,Producer 还一脸无辜地返回成功。所以,我个人习惯生产环境至少用 acks=1,核心数据用 acks=all。

2.3 Broker:消息的存储与中转

Broker 是 Kafka 的服务端节点。一个 Kafka 集群由多个 Broker 组成。每个 Broker 负责存储一部分数据,并处理 Producer 和 Consumer 的请求。

Broker 层面有两个概念特别重要:

  • Topic(主题):消息的逻辑分类。比如订单消息发到 orders Topic,日志消息发到 logs Topic。
  • Partition(分区):Topic 的物理分片。一个 Topic 可以分成多个 Partition,每个 Partition 是一个有序的、不可变的日志文件。

为什么要分区?说白了就是为了水平扩展。如果只有一个 Partition,那吞吐量就受限于单台机器的磁盘和网络。分区之后,多个 Broker 可以并行处理。

概念 作用 丢失风险点
Topic 消息的逻辑分类 配置错误导致数据写入错误 Topic
Partition 物理分片,实现并行 Leader 副本切换时可能丢数据
Replica 副本,保证高可用 ISR 列表过小,未同步副本被剔除

避坑指南: 我曾经见过一个运维同事,把 Topic 的 min.insync.replicas 设成了 1,结果 Leader 挂了,新的 Leader 没有完整数据,消息就丢了。记住,min.insync.replicas 至少要设为 2,配合 acks=all 才能保证不丢。

2.4 Consumer:消息的终点

Consumer 从 Broker 拉取消息进行处理。Consumer 会维护一个 Offset,记录自己消费到了哪个位置。

Consumer 的丢失场景通常发生在:

  • 自动提交 Offset:消息还没处理完,Offset 就提交了。这时候 Consumer 挂了,重启后从已提交的 Offset 开始消费,中间的消息就丢了。
  • Rebalance 期间:分区重新分配,如果 Offset 没处理好,也可能导致重复消费或丢失。

核心原则: 先处理业务逻辑,再提交 Offset。我个人习惯把自动提交关掉,手动控制提交时机。

2.5 Offset:消息的坐标

Offset 是消息在 Partition 中的唯一编号。你可以把它想象成书签——你读到第 100 页,下次就从第 101 页开始读。

Offset 的维护方式有两种:

  1. 自动提交:默认每 5 秒提交一次。简单,但有丢失风险。
  2. 手动提交:由业务代码控制提交时机。安全,但需要开发者自己管理。
// 手动提交示例
while (true) {
    ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
    for (ConsumerRecord<String, String> record : records) {
        // 处理业务逻辑
        process(record);
        // 处理完成后再提交 Offset
        consumer.commitSync();
    }
}

我的建议: 如果你对数据一致性要求高,用 commitSync() 而不是 commitAsync()。虽然同步提交会阻塞,但能保证 Offset 一定提交成功。异步提交快,但回调失败时你可能不知道。

2.6 消息丢失的常见环节总结

咱们把上面讲的串起来,看看消息在哪个环节可能丢:

环节 丢失原因 一句话解决方案
Producer 发送 acks=0 或网络超时 设置 acks=all + 重试机制
Broker 存储 Leader 宕机,副本未同步 min.insync.replicas ≥ 2
Consumer 消费 自动提交 Offset 导致未处理 手动提交,先处理再提交

嗯,这一章的内容就到这里。你只要把 Producer、Broker、Consumer、Topic、Partition、Offset 这几个概念吃透,后面排查消息丢失的时候,你就能快速定位问题出在哪个环节。说白了,Kafka 的消息丢失,90% 的情况都逃不出上面这张表。