如何在Spring JMS中实现UNORDER_ACKNOWLEDGMENT并控制消息确认
我的Spring应用里有多个监听器,部署时偶尔会看到消息:Rejecting received message because of the listener container having been stopped in the meantime.,同时死信队列(DLQ)不断增长。当前用的是默认的自动确认(auto ack)模式,我想改成客户端确认(client ack)来控制消息确认逻辑。
我已经配置了以下Bean:
@Bean public DefaultJmsListenerContainerFactory jmsListenerContainerFactory(SQSConnectionFactory sqsConnectionFactory) { DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory(); factory.setConnectionFactory(sqsConnectionFactory); factory.setDestinationResolver(new DynamicDestinationResolver()); factory.setErrorHandler(this::handleError); factory.setSessionAcknowledgeMode(Session.CLIENT_ACKNOWLEDGE); return factory; } @Bean public JmsTemplate jmsTemplate(SQSConnectionFactory sqsConnectionFactory) { return new JmsTemplate(sqsConnectionFactory); } @Bean public AmazonSQS buildAmazonSqs() { return AmazonSQSClientBuilder.standard().withRegion(region).build(); } @Bean public SQSConnectionFactory sqsConnectionFactory(AmazonSQS amazonSQS) { return new SQSConnectionFactory( new ProviderConfiguration().withNumberOfMessagesToPrefetch(1), amazonSQS); }
但测试发现,就算在@JmsListener里不执行message.acknowledge();,消息还是会从队列中被移除,不会回到队列,通过Datadog追踪能看到消息已被后台删除。把Session.CLIENT_ACKNOWLEDGE换成SQSSession.UNORDERED_ACKNOWLEDGE也没效果。
我想使用UNORDERED_ACKNOWLEDGMENT的原因是,根据AWS文档,CLIENT_ACKNOWLEDGMENT会确认之前的所有消息。
请问是否必须用@SqsListener替代Spring的@JmsListener?如果不需要,怎么在不调用message.acknowledge()的情况下阻止Spring自动确认消息?
不需要替换为@SqsListener
Spring JMS配合AWS SQS完全可以实现客户端手动确认,问题出在容器工厂的额外配置上,默认的DefaultJmsListenerContainerFactory在使用SQS时,有几个关键参数需要调整:
关闭自动确认的隐式行为
默认情况下,DefaultMessageListenerContainer(DefaultJmsListenerContainerFactory创建的容器)会在监听器方法正常执行完成后自动触发确认,哪怕你设置了CLIENT_ACKNOWLEDGE。要禁用这个行为,需要开启事务模式,同时配合指定的确认模式:@Bean public DefaultJmsListenerContainerFactory jmsListenerContainerFactory(SQSConnectionFactory sqsConnectionFactory) { DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory(); factory.setConnectionFactory(sqsConnectionFactory); factory.setDestinationResolver(new DynamicDestinationResolver()); factory.setErrorHandler(this::handleError); // 开启事务模式,禁用容器自动确认 factory.setSessionTransacted(true); // 使用无序确认模式,避免确认当前消息时连带确认之前的所有消息 factory.setSessionAcknowledgeMode(SQSSession.UNORDERED_ACKNOWLEDGE); return factory; }核心是
setSessionTransacted(true),它会让容器放弃自动确认逻辑,完全由你通过message.acknowledge()手动控制消息确认时机。优化预取与并发配置
保留withNumberOfMessagesToPrefetch(1)的设置,避免预取过多未确认消息导致堆积;同时建议限制容器并发数为1(业务允许的情况下),避免多线程场景下的确认混乱:factory.setConcurrency("1");异常场景的消息处理
开启事务模式后,监听器抛出异常时,容器会回滚事务,消息会回到SQS队列(根据队列的可见性超时设置重试),而非直接进入DLQ。你可以通过自定义ErrorHandler来控制异常时的行为,比如添加重试次数限制后再转入DLQ。
之前配置不生效的原因
- 非事务模式下,
DefaultJmsListenerContainerFactory会忽略CLIENT_ACKNOWLEDGE设置,在监听器方法执行完成后自动调用acknowledge(),这是Spring JMS的默认简化行为,和SQS的客户端确认逻辑冲突。 SQSSession.UNORDERED_ACKNOWLEDGE需要配合事务模式才能生效,否则容器的自动确认会覆盖自定义的确认策略。
内容的提问来源于stack exchange,提问作者Bob213

