Spring Kafka详解
定位:轻量消息栈第 02 篇,讲透 Kafka 模型、KafkaTemplate 发送、@KafkaListener 消费、分区顺序与可靠性语义
适用版本:Spring Kafka 3.x
目录
一、Kafka 模型
1.1 三个核心概念
Topic(主题):消息的逻辑分类
└── Partition(分区):主题的物理分片,并行与顺序的基本单位
└── Offset:分区内消息的递增序号,消费位置标记
| 特性 | 说明 |
|---|---|
| 分区内有序 | 同一分区消息按写入顺序;分区间无全局顺序 |
| 日志语义 | 消息写入后保留(按保留策略),可重复消费/回溯 |
| 追加写 | 只追加不修改,天然高吞吐 |
1.2 与 RabbitMQ 的语义差异
| 维度 | RabbitMQ | Kafka |
|---|---|---|
| 消费模型 | 消费即删(任务队列) | 保留可重放(日志) |
| 顺序粒度 | 单队列 | 单分区 |
| 吞吐 | 中等 | 高 |
| 回溯 | 不支持(已消费即丢) | 支持(按 offset/时间) |
二、KafkaTemplate 发送
2.1 基本发送
kafkaTemplate.send("order-topic", order.getId().toString(), orderEvent);
// ↑ key:决定分区
2.2 key 的作用(顺序与均衡)
指定 key:同 key 的消息进同一分区 → 局部顺序保证
不指定 key:轮询/粘性分区分配(均衡优先,无顺序保证)
顺序需求通过 key 表达:同一订单的消息用订单号做 key。
2.3 发送可靠性配置
| 配置 | 作用 |
|---|---|
acks | 0=不等、1=主副本写成功、all=所有同步副本确认(最可靠) |
| retries | 发送失败重试 |
| 幂等生产者 | 防重试导致的重复(enable.idempotence=true) |
CompletableFuture<SendResult<String, OrderEvent>> f = kafkaTemplate.send(...);
f.whenComplete((r, ex) -> { /* 成功/失败处理 */ });
三、@KafkaListener 消费
3.1 基本用法
@KafkaListener(topics = "order-topic", groupId = "order-service")
public void onEvent(OrderEvent event) {
// 业务处理
}
3.2 消费者组机制
消费者组(groupId):
组内:分区被组内消费者分摊(一个分区只被组内一个消费者消费)
组间:各自独立消费全量数据(广播式多订阅)
并发度上限 = 分区数:
3 个分区最多 3 个消费者并行;多于 3 个的消费者空闲
3.3 offset 提交
| 模式 | 行为 |
|---|---|
| 自动提交(默认) | 周期性提交"已拉取"的 offset(可能"已读未处理完") |
| 手动提交 | 处理成功后显式提交(MANUAL/MANUAL_IMMEDIATE) |
手动:处理完成 → ack.acknowledge()
更可靠:避免"拉取后宕机"导致重放窗口过大
批量监听(@KafkaListener(batch = "true"))一次处理一批,批量提交。
四、可靠性语义
4.1 不丢消息
生产端:
acks=all(所有同步副本确认)+ 合理重试 + 幂等生产者
发送回调失败 → 记录/重发/落本地表
消费端:
手动提交:业务处理成功才提交
处理失败 → 重试/死信,不要"跳过即提交"
Broker:
副本数 ≥3、min.insync.replicas ≥2(防主副本丢数据)
4.2 不重复(幂等 + 精确一次)
至少一次是常态(重平衡、处理失败重投)→ 消费幂等必须
精确一次(Exactly Once):
Kafka 事务 + 幂等生产者 + 消费端事务性提交
适用"消费-处理-再生产"链路,成本高,按需使用
五、工程问题
5.1 重复消费
原因:消息已拉取未提交时宕机 → 重启重放;再平衡后分区重新分配
对策:消费幂等(唯一键/状态机),与 MQ 通用纪律一致
5.2 顺序消费
保证同 key 顺序:
① 同 key 同分区(生产端)
② 分区内单线程消费(消费端默认满足——一个分区一个消费者线程)
注意:并发处理分区内消息会破坏顺序
5.3 再平衡(Rebalance)
触发:消费者加入/退出/超时、分区数变化、订阅变化
影响:短暂停止消费、分区重分配、可能重复(未提交的重新消费)
减少:稳定消费者(避免频繁重启)、合理 session/heartbeat 超时
5.4 消费失败与死信
@RetryableTopic(attempts = 3, backoff = @Backoff(delay = 1000))
@KafkaListener(topics = "order-topic")
public void onEvent(OrderEvent e) { ... }
// 重试耗尽 → 自动进入 DLT(死信主题)
5.5 积压处理
扩分区(提升并行上限)+ 加消费者实例(≤ 分区数)
批量消费 + 优化单条处理耗时
监控:消费滞后(lag)指标告警
六、总结
- 模型:Topic→Partition→Offset;分区内有序、日志可回溯;与 Rabbit 的消费即删语义不同。
- 发送:key 决定分区(顺序与均衡的开关);acks=retries+ 幂等生产者保可靠;Future 拿结果。
- 消费:消费者组分摊分区,并发上限 = 分区数;自动提交方便、手动提交可靠。
- 可靠性:生产端 acks + 副本、消费端处理后提交、幂等兜底;精确一次靠事务按需。
- 工程:重复靠幂等、顺序靠 key + 分区单消费、再平衡稳消费、失败用 @RetryableTopic+DLT、积压扩分区加消费者。
七、常见高频面试题
1. Kafka 的 Topic、Partition、Offset 是什么关系?
要点:Topic 是消息的逻辑分类;一个 Topic 分多个 Partition(分区),分区是并行与顺序的基本单位——分区内消息有序,分区间无全局顺序;每条消息在分区内有递增的 Offset(偏移量),标记位置,消费者按 offset 推进消费、可回溯到任意位置。分区决定吞吐上限(并行度)与顺序粒度;日志语义下消息保留可重放,区别于消费即删的队列。
2. Kafka 如何保证消息的顺序?
要点:只在分区内保证顺序,无全局顺序。设计:① 生产端用相同 key 的消息路由到同一分区(默认按 key 哈希)——同一业务实体(如订单号)的消息进同一分区;② 消费端一个分区同时只被组内一个消费者消费,单线程顺序处理。因此"局部顺序"(同 key 有序)是常态,全局顺序需要单分区(牺牲并行)。注意:消费端若对分区内消息做并发处理会破坏顺序。
3. 消费者组(Consumer Group)是怎么工作的?
要点:组是消费的基本单位。组内:Topic 的分区在组内消费者之间分摊——每个分区只被组内一个消费者消费,组内消费者越多并行度越高(上限是分区数,多余消费者空闲)。组间:不同组各自独立消费全量数据(类似广播的多订阅方)。消费者加入/退出/超时会触发再平衡,重新分配分区。这个机制同时支撑"队列式分摊"(同组)与"发布订阅"(多组)。
4. offset 的自动提交和手动提交有什么区别?
要点:自动提交(默认):周期性提交"已拉取"的 offset——简单,但若拉取后未处理完就宕机,重启后这部分不会重放,可能丢处理(已读未处理)。手动提交:业务处理成功后显式提交(ack.acknowledge())——保证"处理完才算消费",失败可重试,但可能重复(处理完未提交宕机)。选择:普通场景自动;要求不丢处理用手动 + 消费幂等兜底重复。批量场景配批量提交减少开销。
5. Kafka 消息重复消费怎么处理?
要点:重复常见:拉取后未提交宕机重放、再平衡后分区重分配、生产端重试(未开幂等)。Kafka 通用语义是至少一次,消费端幂等是正解:唯一键去重(业务键查重)、状态机前置校验(已处理直接返回)、数据库原子操作。更高级:幂等生产者防生产重复;"消费-处理-生产"链路用 Kafka 事务实现精确一次(成本高,按需)。原则同所有 MQ:接受重复投递,保证执行幂等。
6. 什么是再平衡(Rebalance)?有什么影响?
要点:消费者组成员变化(实例加入/退出/崩溃/超时)或订阅/分区变化时,分区在组内重新分配的过程。影响:① 再平衡期间组暂停消费(STW);② 分区易主——原消费者未提交的消息被新消费者重放(重复风险);③ 频繁再平衡造成消费抖动。减少:保持消费者稳定(避免频繁重启)、合理设置 session.timeout/heartbeat、用合作式再平衡(cooperative)。理解再平衡是排查"消费卡顿/重复"的基础。
7. acks 参数是什么?怎么配?
要点:acks 控制生产者等待多少副本确认才算发送成功。0:不等待(可能丢);1:主副本写入成功即确认(主副本宕机未同步则丢);all(-1):等待所有同步副本(ISR)确认,最可靠。配合:acks=all 需要副本数与 min.insync.replicas 合理(如副本 3、ISR 最小 2),否则可能无法写入。权衡:可靠性与延迟/吞吐。关键业务用 acks=all + 幂等生产者 + 重试。
8. 消费积压怎么处理?
要点:先诊断:消费慢(单条耗时、依赖慢)还是生产突增。对策:① 提升并行——增加分区(提升并行上限)+ 增加消费者实例(不超过分区数);② 优化消费——批量消费、异步化处理、优化慢依赖;③ 紧急——临时扩消费者/转储消化。预防:消费滞后(lag)监控告警、容量按峰值预留。注意:扩分区会改变 key 到分区的映射(可能影响顺序假设),且分区数只增不减,规划要前瞻。
9. Spring Kafka 里消费失败怎么处理(重试与死信)?
要点:方案:① @RetryableTopic 注解——配置重试次数与退避,重试耗尽自动转入死信主题(DLT),框架自动建重试主题与死信主题;② 手动配置 ErrorHandler——DefaultErrorHandler 配重试 + 失败后转发死信主题或记录;③ 消费端逻辑内自行处理(落库标记、告警)。要点:死信不丢——进 DLT 后告警 + 人工/补偿处理;重试间隔用退避防风暴;消费幂等保证重试安全。
10. Kafka 和 RabbitMQ 怎么选?
要点:按模型与场景。Kafka:日志语义(保留可回溯重放)、分区高吞吐、按 key 局部顺序——适合大数据流、事件日志、需要重放/回溯、超高吞吐。RabbitMQ:任务队列语义(消费即删)、路由灵活(多种 Exchange)、单条可靠投递成熟——适合业务异步任务、复杂路由、中等吞吐、需要延迟/死信等丰富队列特性。关键区分:要不要回溯重放、吞吐级别、路由复杂度。两者也可共存(不同场景各用)。

转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/weixin_49076592/article/details/169444434



