Producer端消息丢失:acks、retries与网络超时
Producer端丢消息,说白了就是数据从你的应用发出去,但还没到Broker就丢了。我排查过不少线上问题,十有八九都跟三个参数有关:acks、retries、还有网络超时。今天咱们一个一个拆开看。
核心结论:Producer端丢消息,90%是因为acks=0或acks=1,加上retries没配好。剩下的10%,是网络超时和缓冲区满了。
1. acks参数:你的消息到底要等谁确认?
acks这个参数,决定了Producer要等多少个副本确认,才认为消息发送成功。我见过不少新手直接设成0,理由是"这样快"。快是快了,消息丢了都不知道。
| acks值 | 含义 | 丢消息风险 | 适用场景 |
|---|---|---|---|
| acks=0 | 发出去就不管了,不等待任何确认 | 极高 | 日志、监控等可丢数据 |
| acks=1 | 等Leader写入成功就返回 | 中等 | 一般业务,能接受少量丢失 |
| acks=all(或-1) | 等所有ISR副本都写入成功 | 极低 | 金融、订单等关键数据 |
我个人习惯,核心业务必须用acks=all。你想想看,如果Leader刚写完就挂了,Follower还没同步,acks=1的情况下这条消息就丢了。我曾在项目中遇到过,一个支付系统用了acks=1,结果Broker节点重启时丢了十几条订单消息,还好有对账系统发现了。
避坑指南:我曾经以为acks=all就万事大吉了,结果发现如果min.insync.replicas没配好,acks=all也可能丢消息。比如Topic副本数是3,但min.insync.replicas=1,那实际上只要Leader写入就算成功。所以acks=all要配合min.insync.replicas≥2才真正安全。
2. retries配置:失败后你给过机会吗?
网络抖动、Leader选举、磁盘IO打满……这些在分布式系统里太常见了。如果Producer遇到一次失败就直接放弃,那消息必丢。retries就是给消息第二次机会。
默认情况下,Kafka Producer的retries是Integer.MAX_VALUE(2.1+版本),但很多人用的旧版本或者自己改成了0。我见过最离谱的配置:retries=0,还配了acks=all,说"这样最安全"——安全个啥,一次网络波动就全丢了。
我的建议:retries别设成0,也别设太大。一般设3-5次就够了。同时要配合retry.backoff.ms(默认100ms),让每次重试之间有个间隔,别把Broker打死了。
// 推荐配置示例
Properties props = new Properties();
props.put("acks", "all");
props.put("retries", 3);
props.put("retry.backoff.ms", 500); // 重试间隔500ms
props.put("max.in.flight.requests.per.connection", 1); // 防止重试导致乱序
这里有个坑:max.in.flight.requests.per.connection。如果这个值大于1,且启用了重试,那消息顺序可能乱掉。比如你发了A、B两条消息,A失败了要重试,B成功了,结果A重试成功后反而排在B后面。我建议关键业务把这个值设成1,保顺序。
3. 网络超时与重试机制
光配了retries还不够,超时时间也得对。request.timeout.ms默认是30000ms(30秒),但如果你把retries设成5次,每次重试间隔500ms,那30秒可能不够用。
为什么会这样?因为一次完整的发送流程是:
- Producer发送请求到Broker
- Broker处理并返回响应
- 如果超时没收到响应,触发重试
- 重试又发一次请求
如果request.timeout.ms设得太小,重试还没完成就超时了,那重试机制等于白配。我建议把request.timeout.ms设成:retries × (retry.backoff.ms + 网络往返时间),再留点余量。
实战公式:request.timeout.ms ≥ retries × (retry.backoff.ms + 2000ms)。比如retries=3,retry.backoff.ms=500,那timeout至少设成3×(500+2000)=7500ms。我一般直接设成15000ms,省心。
4. 还有一个容易被忽略的:buffer.memory
Producer端有个发送缓冲区,默认32MB。如果消息发送速度大于Broker处理速度,缓冲区满了怎么办?默认行为是阻塞,但如果你把block.on.buffer.full设成了false(旧版本),或者max.block.ms设得太小,那就会直接抛异常丢消息。
我记得有一次排查线上问题,发现Producer日志里全是"org.apache.kafka.common.errors.TimeoutException",一看buffer.memory才16MB,max.block.ms=1000ms。高峰期流量一上来,缓冲区满了,消息发不出去直接超时丢弃。
我的经验:buffer.memory至少设成64MB,如果流量大可以到128MB或256MB。max.block.ms设成60000ms(60秒),给足等待时间。同时监控"buffer-available-percent"这个指标,如果经常低于20%,就该扩容了。
5. 总结一下排查思路
如果你怀疑Producer端丢消息,按这个顺序查:
- 第一步:看acks。如果是0或1,先改成all试试。
- 第二步:看retries。如果是0,改成3-5次。
- 第三步:看request.timeout.ms。算一下够不够重试用。
- 第四步:看buffer.memory和max.block.ms。有没有被撑爆。
- 第五步:看Producer日志。有没有TimeoutException、NetworkException。
嗯,这三个参数配好了,Producer端丢消息的概率能降90%以上。剩下的10%,咱们后面章节再聊——比如幂等性、事务、Broker端丢消息那些事。