关于Artemis中max-delivery-attempts对未确认消息失效的技术咨询
嘿,你遇到的这个情况其实不是Artemis的bug,而是它针对消息确认和会话回滚的设计逻辑导致的,我来一步步给你理清楚:
为什么未确认就回滚会让max-delivery-attempts失效?
当你把autoCommitAcks设为false,收到消息后没调用acknowledge()就直接回滚会话时,Artemis会把这次投递当成“未完成的投递”——说白了,broker觉得这条消息只是发去了消费者,但消费者没真正“接收并确认处理”,所以会把消息放回队列,而且不会递增deliveryCount(一直是1),那max-delivery-attempts的重试限制自然就不会触发,消息就被无限重发了。
这个设计的初衷是为了避免消息丢失:如果消费者还没确认就崩溃或者回滚,broker没法确定消费者到底有没有处理过消息,所以干脆把消息当作“从没成功投递过”来处理,保证消息不会凭空消失。
消息确认到底是干嘛的?
在Artemis的Core API里,ClientMessage#acknowledge()的核心作用是给broker发一个明确的“已处理完成”信号:这条消息我已经收到,并且业务逻辑处理完了,你不用再给我发了,也可以把它从队列里清掉了。
它可不是单纯的“我收到消息了”的标记——broker本来就知道消息已经发出去了,确认是最终的“处理收尾”回执。一旦broker收到这个确认,就会更新消息状态(比如从队列移除)。
要是你先确认了消息,再回滚会话,Artemis会撤销这个确认,把消息放回队列,而且这时候会递增deliveryCount(因为之前的投递已经被确认过,哪怕后来回滚了,也算一次完整的投递尝试),所以max-delivery-attempts就正常生效了。
能不能灵活用acknowledge()?
必须可以!这就是autoCommitAcks=false的意义啊——你完全可以自己控制什么时候确认消息,适配不同的业务场景:
- 快速确认:收到消息就立刻调用
acknowledge(),和autoCommitAcks=true的效果一样,适合那些不需要严格保证处理成功的场景; - 处理成功再确认:等业务逻辑跑完没异常了再确认,要是处理失败就回滚会话(或者调用
negativeAcknowledge()),这样消息会被重新投递,适合必须保证消息至少被处理一次的场景; - 批量确认:Artemis的会话确认是累积式的,调用一次
acknowledge()会确认当前会话里所有没确认的消息,你可以攒一批消息处理完再统一确认,减少网络来回的开销。
额外提示:怎么让重试限制生效?
如果你想在处理失败时触发max-delivery-attempts,别直接回滚会话,应该调用ClientMessage#negativeAcknowledge()(或者对应版本的ClientConsumer#reject())。这个方法是明确告诉broker:“我收到消息了,但处理失败了,麻烦再发我一次”,这时候broker才会递增deliveryCount,等达到max-delivery-attempts的阈值,就会把消息转到死信队列(如果配置了的话)。
内容的提问来源于stack exchange,提问作者Mariusz

