启用Exactly Once后PubSub在Ack Deadline前重发消息的疑问
我配置了一个启用Exactly Once Delivery和指数重试的Pub/Sub订阅,具体参数如下:
- 指数重试:最小退避10秒、最大退避600秒
- Ack Deadline:初始设为60秒,二次测试改为120秒
使用以下Java代码(基于Spring Cloud GCP的pubSubTemplate)读取订阅,故意不执行acking操作模拟故障:
Subscriber subscriber = pubSubTemplate.subscribe("projects/PROJECT_ID/subscriptions/sushil-exactly-once-topic-sub", (basicAcknowledgeablePubsubMessage -> { PubsubMessage message = basicAcknowledgeablePubsubMessage.getPubsubMessage(); String data = message.getData().toStringUtf8(); String id = message.getMessageId(); log.info("Message {}", data); })); subscriber.startAsync();
测试发现首次重发始终在23-26秒左右,多次结果一致:
2023-07-13T11:22:04.967+05:30 INFO 66438 --- [sub-subscriber1] c.w.e.PubSubStandaloneConfiguration : Message Message-1 2023-07-13T11:22:30.715+05:30 INFO 66438 --- [sub-subscriber1] c.w.e.PubSubStandaloneConfiguration : Message Message-1 2023-07-13T11:23:47.788+05:30 INFO 66438 --- [sub-subscriber2] c.w.e.PubSubStandaloneConfiguration : Message Message-1
首次重发后的交付均在60秒期限耗尽后进行,用spring-integration和基础消费者测试行为一致。原以为首次重发应在60秒后,想确认此是否为正常行为,还是理解有误或存在Bug?
这种首次重发提前的行为是正常的,并非Bug,核心原因是Pub/Sub Java客户端的自动重试逻辑与Exactly Once Delivery订阅的特性共同作用:
首次重发的触发逻辑
当消费回调仅完成日志打印、未执行ack/nack时,客户端会判定消息处理已结束但未确认。此时不会触发Ack Deadline的自动延期(延期仅针对仍在处理中的长耗时消息),而是结合你配置的最小退避10秒,加上消息状态同步、租赁释放的额外延迟(约13-16秒),最终首次重发落在23-26秒区间,与你的测试结果完全匹配。后续重发的规则
首次重发后,客户端会严格遵循Exactly Once Delivery订阅的租赁期(即你设置的Ack Deadline),同时叠加指数退避的延迟。因此后续重发会在租赁期到期后触发,这也是你看到首次之后重发间隔约60秒的原因。Ack Deadline调整的影响
即使将Ack Deadline改为120秒,首次重发的时间仍会维持在23-26秒左右(由最小退避+额外延迟决定),而后续重发则会在120秒租赁期到期后进行,这符合二次测试的预期。
如果需要严格等待Ack Deadline到期后再触发首次重发,可以通过调整客户端配置禁用自动重试逻辑,改为仅依赖租赁期到期触发重发,但这可能会降低消息交付的及时性。
内容的提问来源于stack exchange,提问作者kaysush

