Spring Boot中SQS JMS消息失败后未重试的问题排查
问题描述
我在Spring Boot项目中用JMS对接AWS SQS,现有配置和接收器代码如下:
SQS/JMS配置代码
private final SQSConnectionFactory sqsConnectionFactory = new SQSConnectionFactory( new ProviderConfiguration().withNumberOfMessagesToPrefetch(10), AmazonSQSClientBuilder.defaultClient()); @Bean public DefaultJmsListenerContainerFactory jmsListenerContainerFactory() { DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory(); factory.setConnectionFactory(this.sqsConnectionFactory); factory.setDestinationResolver(new DynamicDestinationResolver()); factory.setSessionAcknowledgeMode(Session.CLIENT_ACKNOWLEDGE); return factory; }
消息接收器代码
@Service public class HandleMessage { @Transactional @JmsListener(destination = "${sqs.handler}") public void receive(String message) throws IOException, JMSException { // 业务处理逻辑 if (message.contains("test")) throw new JMSException("boom!"); // 其他处理 } }
现在遇到的问题是:所有消息都被处理,包含"test"的消息抛出异常后直接消失,完全没触发重试。想请教是否需要修改SQS配置?另外@Transactional注解可能非必需,我的需求是Spring Boot在出现异常时通知SQS消息处理失败,让消息能自动重试。当前SQS的属性如下:
- 最大消息大小:256 KB
- 消息保留周期:4天
- 默认可见性超时:30秒
- 传递延迟:0秒
- 接收消息等待时间:0秒
- 基于内容的去重:未启用
问题分析
出现这个问题的核心原因是**@Transactional注解干扰了SQS消息的确认逻辑**,再结合当前的JMS会话确认模式设置,导致异常抛出后消息被错误地确认并删除:
- @Transactional的影响:给JMS监听方法加@Transactional后,Spring会把JMS会话绑定到Spring事务上下文。一旦方法抛出异常,Spring会触发事务回滚,但AWS SQS的JMS客户端对事务的处理和传统JMS不同——回滚操作并不会让消息重新回到队列,反而可能直接标记为已处理。
- CLIENT_ACKNOWLEDGE模式的误区:当前配置的
Session.CLIENT_ACKNOWLEDGE模式下,正常情况Spring会在方法成功执行后自动确认消息,但结合@Transactional后,事务回滚逻辑会覆盖默认的异常处理逻辑,导致消息被意外确认。
另外你当前的SQS基础配置(比如30秒可见性超时、4天保留周期)是合理的,不需要修改这些属性,核心问题在Spring的JMS配置和代码逻辑上。
解决方案
按照以下步骤调整,就能实现异常时通知SQS重试消息:
1. 移除@Transactional注解
既然你提到这个注解非必需,直接去掉它是关键。这样Spring的JMS listener容器会按照默认的异常处理逻辑来处理消息确认:
@Service public class HandleMessage { // 移除@Transactional @JmsListener(destination = "${sqs.handler}") public void receive(String message) throws IOException, JMSException { // 业务处理逻辑 if (message.contains("test")) throw new JMSException("boom!"); // 其他处理 } }
2. 调整JMS容器的会话确认模式(可选但推荐)
将会话确认模式改为Session.SESSION_TRANSACTED,让Spring通过事务提交/回滚来控制消息的确认:
@Bean public DefaultJmsListenerContainerFactory jmsListenerContainerFactory() { DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory(); factory.setConnectionFactory(this.sqsConnectionFactory); factory.setDestinationResolver(new DynamicDestinationResolver()); // 改为SESSION_TRANSACTED模式 factory.setSessionAcknowledgeMode(Session.SESSION_TRANSACTED); return factory; }
这种模式下,方法成功执行时Spring会提交事务,SQS会标记消息为已处理;方法抛出异常时事务回滚,消息会在可见性超时(30秒)后重新回到队列,触发重试。
3. 配置重试次数与死信队列(可选)
如果需要限制重试次数,避免消息无限循环,可以给SQS配置死信队列(DLQ):
- 创建一个死信队列,作为异常消息的最终存储地
- 在原队列的红rive策略中设置
maxReceiveCount(比如5次),当消息被重试超过5次后,自动转移到死信队列
这样既保证了异常消息的重试机会,又不会让无效消息一直占用队列资源。
内容的提问来源于stack exchange,提问作者Jim
相关产品推荐
相关产品推荐

