我有一个简单的Spring Boot服务,该服务使用JMSTemplate侦听AWS SQS队列。正确处理消息后,一切都会按预期进行。
我正在使用CLIENT_ACKNOWLEDGE,因此在处理过程中引发异常时,将再次接收到该消息。但是,将忽略SQS队列上的“默认可见性超时”设置,并且会立即再次接收到该消息。
为SQS队列配置了30秒的默认可见性超时,并在将消息放入DLQ之前接收到20的重新驱动策略。
我已禁用该服务,并使用SQS控制台来验证是否正确设置了“默认可见性超时”。我还尝试过将JMS消息添加到方法签名中并执行手动验证。
这是JMS配置的代码:
@Configuration
@EnableJms
class JmsConfig
{
@Bean
@Conditional(AWSEnvironmentCondition.class)
public SQSConnectionFactory connectionFactory(@Value("${AWS_REGION}") String awsRegion)
{
return new SQSConnectionFactory(
new ProviderConfiguration(),
AmazonSQSClientBuilder.standard()
.withRegion(Regions.fromName(awsRegion))
.withCredentials(new DefaultAWSCredentialsProviderChain())
);
}
@Bean
public DefaultJmsListenerContainerFactory jmsListenerContainerFactory(ConnectionFactory connectionFactory)
{
DefaultJmsListenerContainerFactory factory =
new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
factory.setDestinationResolver(new DynamicDestinationResolver());
factory.setConcurrency("3-10");
factory.setSessionAcknowledgeMode(Session.CLIENT_ACKNOWLEDGE);
factory.setErrorHandler(defaultErrorHandler());
return factory;
}
@Bean
public ErrorHandler defaultErrorHandler()
{
return new ErrorHandler()
{
@Override
public void handleError(Throwable throwable)
{
LOG.error("JMS message listener error: {}", throwable.getMessage());
}
};
}
@Bean
public JmsTemplate defaultJmsTemplate(ConnectionFactory connectionFactory)
{
return new JmsTemplate(connectionFactory);
}
}
这是侦听器的代码:
@Component
public class MessagingListener
{
@Autowired
private MessageService _messageService;
@Autowired
private Validator _validator;
@JmsListener(destination = "myqueue")
public void receiveMessage(String messageJson)
{
try
{
LOG.info("Received message");
// The following line throws an IOException is the message is not JSON.
MyMessage myMessage = MAPPER.readvalue(messageJson, MyMessage.class);
Set<ConstraintViolation<MyMessage>> _validator.validate(myMessage);
if (CollectionUtils.isNotEmpty(violations))
{
String errorMessage = violations.stream()
.map(v -> String.join(" : ", v.getPropertyPath().iterator().next().getName(),
v.getMessage()))
LOG.error("Exception occurred while validating the model, details: {}", errorMessage)
throw new ValidationException(errorMessage);
}
}
catch (IOException e)
{
LOG.error("Error parsing message", e);
throw new ValidationException("Error parsing message, details: " + e.getMessage());
}
}
}
当使用无效JSON或未通过验证的JSON将消息放置在SQS队列上时,该消息会很快收到20次,然后在DLQ上结束。要遵守SQS中的“默认可见性超时”设置,需要做些什么?
答案 0 :(得分:1)
如果发生异常,则通过ChangeMessageVisibility将失败消息的可见性超时设置为0,因此即使队列具有不同的visibilityTimeout
设置,SQS也会立即发送此消息。
这是怎么发生的?
您可以看到here,Spring JMS的AbstractMessageListenerContainer
简要地做到了:
try {
invokeListener(session, message); // This is your @JMSListener method
}
catch (JMSException | RuntimeException | Error ex) {
rollbackOnExceptionIfNecessary(session, ex);
throw ex;
}
commitIfNecessary(session, message);
在rollbackOnExceptionIfNecessary
方法上,将调用session.recover(),因为:
session.getTransacted()
始终为false,因为SQS不支持事务。参见here。isClientAcknowledge(session)
将返回true,因为您使用的是CLIENT_ACKNOWLEDGE模式。最后,recover() of SQSSession 否定确认消息,这意味着将该特定消息的visibilityTimeout
设置为0,会导致SQS立即尝试发送该消息。
覆盖此行为的最简单方法是实现CustomJmsListenerContainerFactory
和CustomMessageListenerContainer
,而不是使用DefaultJmsListenerContainerFactory
和DefaultMessageListenerContainer
。
public class CustomMessageListenerContainer extends DefaultMessageListenerContainer {
public CustomMessageListenerContainer() {
super();
}
@Override
protected void rollbackOnExceptionIfNecessary() {
// do nothing, so that "visibilityTimeout" will stay same
}
}
public class CustomJmsListenerContainerFactory {
@Override
protected DefaultMessageListenerContainer createContainerInstance() {
return new CustomMesageListenerContainer();
}
}
使用@Component
或像在JmsConfig
中那样将其设置为Spring bean:
@Bean
public DefaultJmsListenerContainerFactory jmsListenerContainerFactory(ConnectionFactory connectionFactory) {
DefaultJmsListenerContainerFactory factory = new CustomJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
// and set other stuff on factory
return factory;
}
注意:
如果您的应用程序正在通过JMS的SQS使用其他类型的数据源,请确保为它们使用不同的Container和ContainerFactory,以便rollbackOnExceptionIfNecessary
表现正常。