GCP环境下应用重复接收已确认的Google Pub/Sub消息排查求助
遇到这种偶发的Pub/Sub重复消息问题确实挺头疼的,我结合自己踩过的坑和Pub/Sub的核心机制,给你梳理几个排查方向:
1. 先确认ACK操作是否真的触达了Pub/Sub服务端
你的代码里在ACK回调里打了日志,但这只能说明本地回调执行了,不能代表Pub/Sub服务端已经成功接收到ACK请求:
- 可以在ACK回调里加上更多上下文日志,比如打印
pubSubMessage.getMessageId(),确认操作的是目标消息; - 用抓包工具(比如
tcpdump)在应用机器上监控到Pub/Sub服务端的HTTP/2请求,验证ACK请求是否真的发送出去; - 检查有没有网络层面的问题,比如防火墙规则是否限制了应用和Pub/Sub服务端的通信,或者是否存在请求超时的情况。
2. 检查订阅的ACK Deadline配置
Pub/Sub的订阅有个ackDeadline参数(默认10秒),如果你的消息处理时间超过这个值,服务端会认为消息处理失败,自动重新投递,哪怕你最后调用了ack():
- 去GCP控制台查看订阅的
ackDeadline设置,对比消息从接收到完成ACK的耗时; - 确认Spring Cloud GCP的Pub/Sub集成是否开启了自动延长ACK Deadline的机制,如果没有,长时间处理的消息很容易触发重复投递。
3. 排查去重逻辑的潜在漏洞
你的去重实现用了IdempotentReceiverInterceptor,这里有几个要注意的点:
- 去重键的唯一性:你用
objectId和objectGeneration作为键,要确认这两个字段组合起来是否能唯一标识一条消息?有没有可能不同消息生成了相同的键? - 去重存储的可靠性:
idempotencyStore()如果是内存实现,应用重启、多实例部署都会导致去重状态丢失;另外,存储的键有没有设置合理的过期时间?如果过期太快,之前处理过的消息会被重新识别为新消息; - ACK回调里的移除操作:你在ACK成功/失败回调里都调用了
removeFromIdempotentStore(targetMessage, false);,这可能是关键问题!正常来说,去重存储应该保留已处理消息的键来防止重复,你把它移除了,后续重复投递的消息就会绕过去重逻辑,被重新处理。
4. 核对Pub/Sub服务端的指标和日志
去GCP控制台查看订阅的监控指标:
- 看
ack_message_count是否和你的ACK日志数量匹配; - 查看
redelivered_message_count的变化,确认重复投递的来源; - 在GCP Logging里搜索Pub/Sub相关的错误日志,排查是否有服务端层面的ACK丢失或异常。
5. 排查应用的并发和线程问题
你的日志显示不同线程(sub-subscriber3和sub-subscriber1)处理同一条消息,说明是多线程消费:
- 检查是否存在并发竞态,比如线程A还没完成去重存储的写入,线程B已经接收到重复消息并开始处理;
- 确认ACK操作和去重逻辑的执行顺序是否正确,有没有可能去重逻辑还没生效,ACK已经被调用了?
6. 改用Pub/Sub原生的MessageID做去重键
Pub/Sub每条消息都有服务端生成的唯一messageId,比你自定义的objectGeneration更可靠:
- 尝试把
messageId加入到去重键中,看看是否能减少重复; - 在日志里打印
messageId,确认重复接收的是不是同一个messageId,以此区分是真的重复投递,还是内容相似的不同消息。
内容的提问来源于stack exchange,提问作者gstackoverflow
相关产品推荐
相关产品推荐

