Kafka 消息可靠性:发送确认、acks、副本保存与Offset手动提交
Kafka 经常被用在日志采集、用户行为、订单事件、数据同步和异步解耦场景。它的吞吐量很高,但高吞吐不等于天然不丢消息。面试里问“Kafka 如何保证消息不丢”,要沿着生产者、Broker、消费者三段链路讲。
一句话概括:Kafka 可靠性要从生产者发送、Broker 副本保存、消费者 Offset 提交三个位置兜住;生产者用回调和重试确认发送结果,Broker 用acks=all和副本机制保证保存,消费者关闭自动提交,处理成功后再提交 Offset。
Kafka 消息会丢在哪里
课件里把 Kafka 消息丢失拆成三段:
| 位置 | 可能问题 | 解决方向 |
|---|---|---|
| 生产者到 Broker | 发送失败但业务没感知 | 异步回调、失败重试 |
| Broker 存储 | Leader 收到但副本没同步就故障 | acks=all、副本机制 |
| 消费者消费 | Offset 先提交,业务后失败 | 关闭自动提交,手动提交 |
所以 Kafka 的可靠性不是一个参数,而是一条端到端链路。
生产者发送可靠性
生产者发送消息有同步和异步两种方式。同步发送可以直接拿到结果,但吞吐较低;异步发送吞吐更高,必须在回调里处理失败。
// 异步发送,失败时要记录日志、告警或进入补偿重试。kafkaProducer.send(record,newCallback(){@OverridepublicvoidonCompletion(RecordMetadatametadata,Exceptione){if(e!=null){System.out.println("消息发送失败,记录日志或入库补偿");return;}longoffset=metadata.offset();intpartition=metadata.partition();Stringtopic=metadata.topic();}});同时可以配置重试次数:
props.put(ProducerConfig.RETRIES_CONFIG,10);但要注意:重试可能导致重复消息或顺序变化。所以业务消费者仍然要做幂等,顺序敏感场景还要配合 key、分区和生产者相关配置一起设计。
如果使用较新的 Kafka 客户端,生产者幂等通常也要一起考虑:
props.put(ProducerConfig.ACKS_CONFIG,"all");props.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG,true);props.put(ProducerConfig.RETRIES_CONFIG,Integer.MAX_VALUE);props.put(ProducerConfig.MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION,5);生产者幂等解决的是“生产者重试导致同一条消息被写入多次”的问题。它不是业务幂等的替代品,因为业务重复消费、消费者重试、人工补偿仍然可能让同一业务事件被处理多次。
Broker 存储可靠性:acks
acks决定生产者什么时候认为消息发送成功。
| 配置 | 含义 | 可靠性 | 性能 |
|---|---|---|---|
acks=0 | 不等待服务器响应 | 最差 | 最快 |
acks=1 | Leader 收到就返回成功 | 中等 | 较快 |
acks=all | ISR 中副本都确认后才返回成功 | 最高 | 较慢 |
如果业务更关注可靠性,优先选择acks=all。它的意思不是“所有 Broker 都保存”,而是所有需要同步的副本确认后才返回成功。
不过,acks=all还需要和min.insync.replicas一起看。
| 配置 | 位置 | 作用 |
|---|---|---|
replication.factor | Topic | 一个分区有多少副本 |
acks=all | Producer | Leader 等待 ISR 副本确认后再返回 |
min.insync.replicas | Broker 或 Topic | 写入成功至少需要多少个同步副本 |
常见可靠配置是:replication.factor=3、min.insync.replicas=2、生产者acks=all。这样至少要有 2 个同步副本确认,生产者才认为写入成功。
如果 ISR 数量不足还继续写入,就可能在 Leader 故障时丢消息。所以强可靠场景宁愿让生产者收到异常并重试或降级,也不要静默写入一个没有足够副本保护的数据。
消费者 Offset 提交
Kafka 消费者通过 Offset 记录自己消费到哪里。默认情况下,消费者会自动定期提交 Offset,比如每隔 5 秒提交一次。
自动提交的问题是:Offset 可能已经提交了,但业务还没处理成功。
更稳的方式是关闭自动提交,业务处理成功后再手动提交 Offset。
常见提交方式:
| 方式 | 特点 |
|---|---|
| 同步提交 | 确认提交成功再继续,可靠但会阻塞 |
| 异步提交 | 不阻塞,性能好,但失败处理弱 |
| 同步 + 异步组合 | 正常异步,关闭或再均衡前同步兜底 |
重复消费怎么处理
关闭自动提交后,消息丢失风险降低,但重复消费仍然可能出现。
比如消费者业务处理成功,还没来得及提交 Offset 就宕机。重启后会从旧 Offset 再消费一次。
Kafka 的基本语义更接近“至少一次”。如果要业务正确,消费者必须幂等:
| 幂等方式 | 示例 |
|---|---|
| 业务唯一键 | 订单 ID、支付 ID、流水号 |
| 去重表 | 保存已处理 messageId |
| 数据库唯一索引 | 重复插入直接失败 |
| 状态机判断 | 已支付、已发货、已完成则不重复处理 |
面试回答模板
可以这样答:
Kafka 保证消息不丢要从三个层面讲。生产者发送到 Broker 可能失败,所以我们会用异步发送回调判断结果,失败时记录日志或补偿重发,并配置 retries;为了避免生产者重试写出重复消息,还可以开启
enable.idempotence=true。Broker 存储阶段通过副本机制保证可靠性,生产端配置acks=all,Topic 通常配置多副本,并配合min.insync.replicas,比如 3 副本下至少 2 个 ISR 确认后才算写入成功。消费者侧默认自动提交 Offset 可能导致业务没处理完 Offset 已提交,所以一般关闭自动提交,业务处理成功后手动提交 Offset,可以用同步提交或同步加异步组合。最后要补充 Kafka 仍然可能重复消费,比如处理成功但 Offset 没提交就宕机,所以业务必须做幂等。
小结
Kafka 可靠性回答要抓住三句话:
