为什么要理解消息链路
Kafka 这种组件,刚开始学的时候很容易记成几个名词:Producer、Consumer、Topic、Partition、Broker、Offset。真正用到项目里以后才会发现,Kafka 的难点不在于“会不会发消息”,而在于吞吐量、顺序性、可靠性和消费进度之间的取舍。
在供应链、订单、库存、ERP 同步这类系统里,Kafka 很常见。比如订单创建后通知库存系统预占库存,采购单状态变化后通知财务系统生成应付记录,仓库出库后通知报表系统刷新数据。这些业务都有一个共同点:消息量可能很大,但业务又不能随便丢。
所以理解 Kafka,不能只看 API,要看完整链路:生产者怎么把消息写进去,Broker 怎么存,消费者怎么拉取,offset 怎么提交,失败时怎么恢复。
生产、存储与消费的核心机制
Kafka 的核心设计可以概括成三句话。
第一,生产者不是一条一条傻发,而是会把消息按 topic 和 partition 组织起来,经过序列化、分区选择、批量缓存后再发送给 Broker。
第二,Broker 不是把消息存在一个普通队列里,而是把消息追加到分区日志中。分区是 Kafka 并行能力的基础,日志追加是它高吞吐的基础。
第三,消费者不是等 Broker 推消息,而是主动 poll 拉取。消费者属于某个 consumer group,同一个 group 里一个分区同一时刻通常只会分给一个消费者处理,处理进度通过 offset 记录。
Kafka 的吞吐量来自批量、顺序写、分区并行和零拷贝等机制;可靠性来自副本、ack、幂等、事务和 offset 提交策略。项目里真正要做的,是根据业务重要性选择合适参数,而不是一味追求“最快”。
供应链事件处理示例
先看生产者。生产者发送一条消息时,大致会经历这些步骤:
- 业务代码构造消息,比如订单号、业务类型、变更时间。
- 序列化,把对象转成字节。
- 选择分区,如果指定 key,通常会根据 key hash 到固定 partition。
- 放入本地缓冲区,按 batch 组织。
- Sender 线程把 batch 发给对应 Broker。
- Broker 追加日志并根据 ack 策略返回结果。
如果要增大生产吞吐量,常见方向有几个。
batch.size 可以调大,让更多消息合并成一个批次。批量越充分,网络请求次数越少。
linger.ms 可以适当增加,让生产者多等几毫秒凑批次。它会牺牲一点延迟,换更高吞吐。
compression.type 可以使用 lz4、snappy 或 zstd,减少网络传输和磁盘占用。消息体较大时效果明显。
分区数要足够。一个 topic 如果只有一个 partition,再多消费者也无法在同一个 consumer group 内并行消费这个 topic。
生产者可以设置 acks=all、开启幂等 enable.idempotence=true,并合理配置重试。这样可以在 Broker 短暂失败时自动恢复,同时避免重试造成重复写入。
再看消费者。消费者是通过 poll 拉取消息,处理后提交 offset。这里最关键的是 offset 提交时机。
如果先提交 offset 再处理业务,消费者宕机后这批消息可能永远不会再处理,容易丢消息。
如果先处理业务再提交 offset,宕机后可能重复消费,但至少消息不会丢。大多数核心业务更接受“重复但可幂等”,而不是“直接丢”。
所以在订单、库存这类场景里,我更倾向于手动提交 offset:
1 | poll 消息 |
为了防止重复消费,业务侧要做幂等。比如用消息唯一 ID 建一张消费记录表,或者让订单状态流转本身具备幂等判断:已经处理过的状态不再重复扣减库存。
可靠性与顺序性风险
第一个坑,是只调大分区数,不看消费者处理能力。分区数增加能提高并行度,但也会带来更多文件句柄、更多 leader 选举和更复杂的再均衡。分区不是越多越好。
第二个坑,是为了吞吐把 acks 调成 0。这样生产者发出去就不管了,速度很快,但 Broker 是否收到并不确定。日志、埋点可以这么考虑,订单状态、库存变更就不应该这么随意。
第三个坑,是自动提交 offset。自动提交很方便,但它提交的是消费进度,不是业务成功。只要业务处理和 offset 提交之间没有绑定,就要接受消息丢失或重复的风险。
第四个坑,是忽略 rebalance。消费者数量变化、心跳超时、poll 时间过长,都可能触发再均衡。处理单条消息耗时很长时,要注意 max.poll.interval.ms 和批量大小,避免消费者被踢出 group。
第五个坑,是把 Kafka 当数据库。Kafka 适合做日志流和消息流,不适合承担复杂查询。业务状态仍然要落到数据库、缓存或搜索系统中。
总结
Kafka 的生产者负责高效、可靠地把消息写入分区日志;消费者负责按分区拉取消息、处理业务并提交 offset。吞吐量靠批量、压缩、分区和顺序写;可靠性靠副本、ack、幂等、事务和手动提交。
在真实项目里,我会按业务重要性分层:日志类消息可以优先吞吐,核心业务消息优先可靠;允许重复,但不能无声丢失。只要这条原则清楚,Kafka 参数就不会乱调。