How to prevent a specific exception from being retried in RabbitMQ - spring-boot

In my Spring Boot project I'm using RabbitMQ for messaging and have two exceptions CustomExceptionA and CustomExceptionB. I want my CustomExceptionA to be retried n times and CustomExceptionB not to retry but directly sent to DLQ.
Below are my configurations:-
yaml file
spring:
rabbitmq:
listener:
simple:
default-requeue-rejected: false
retry:
enabled: true
initial-interval: 2s
max-attempts: 3
max-interval: 2s
multiplier: 1
Configuration file
#Bean
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(ConnectionFactory connectionFactory,
SimpleRabbitListenerContainerFactoryConfigurer configurer) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
configurer.configure(factory, connectionFactory);
factory.setErrorHandler(errorHandler());
return factory;
}
#Bean
public ErrorHandler errorHandler() {
return new ConditionalRejectingErrorHandler(customExceptionStrategy());
}
#Bean
FatalExceptionStrategy customExceptionStrategy() {
return new CustomFatalExceptionStrategy();
}
-----------------------------------------------------------------------------------------------------------------------------
#Component
public class CustomFatalExceptionStrategy extends ConditionalRejectingErrorHandler.DefaultExceptionStrategy {
#Override
public boolean isFatal(Throwable throwable) {
return (throwable.getCause() instanceof CustomExceptionB);
}
}
According to the blog : https://www.baeldung.com/spring-amqp-error-handling mechanism should work but for some reason it is not working for me.
Someone please look at the issue.

Related

Spring Boot RabbitMQ ConnectException:

I have an application that should work with rabbitmq. I have RabbitMQConfig, which tries to connect with the rabbit, but if it fails to connect, the application does not start at all.
What I want to achieve is that after launching the application, it will try to connect to the rabbit and if it manages to connect, I will start functionality to create a queue and listen to it accordingly. Currently at startup if the rabbit is available and then disappears it starts throwing "java.net.ConnectException: Connection refused". Can I catch this error and how, both at startup and during the operation of the application.
This is config file:
public class RabbitMQConfig implements RabbitListenerConfigurer {
private ConnectionFactory connectionFactory;
#Autowired
public RabbitMQConfig(ConnectionFactory connectionFactory) {
this.connectionFactory = connectionFactory;
}
#Override
public void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) {
registrar.setMessageHandlerMethodFactory(messageHandlerMethodFactory());
}
#Bean
MessageHandlerMethodFactory messageHandlerMethodFactory() {
DefaultMessageHandlerMethodFactory messageHandlerMethodFactory = new DefaultMessageHandlerMethodFactory();
messageHandlerMethodFactory.setMessageConverter(consumerJackson2MessageConverter());
return messageHandlerMethodFactory;
}
#Bean
public MappingJackson2MessageConverter consumerJackson2MessageConverter() {
return new MappingJackson2MessageConverter();
}
#Bean
public RabbitTemplate rabbitTemplate() {
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());
return rabbitTemplate;
}
#Bean
public AmqpAdmin amqpAdmin() {
return new RabbitAdmin(connectionFactory);
}
#Bean(name = "rabbitListenerContainerFactory")
public SimpleRabbitListenerContainerFactory simpleRabbitListenerContainerFactory(
SimpleRabbitListenerContainerFactoryConfigurer configurer,
ConnectionFactory connectionFactory) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
FixedBackOff recoveryBackOff = new FixedBackOff(10000,FixedBackOff.UNLIMITED_ATTEMPTS);
factory.setRecoveryBackOff(recoveryBackOff);
configurer.configure(factory, connectionFactory);
return factory;
}
Whit this methods i create and start listener:
#Service
public class RabbitMQService {
#RabbitListener(queues = "${queueName}", autoStartup = "false", id = "commandQueue")
public void receive(CommandDataDTO commandDataDTO) {
public void receive(Object rMessage) {
doSomething(rMessage);
}
public void createQueue() {
Queue queue = new Queue("queueName"), true, false, false);
Binding binding = new Binding("queueName"),
Binding.DestinationType.QUEUE, env.getProperty("spring.rabbitmq.exchange"),"rKey",
null);
admin.declareQueue(queue);
admin.declareBinding(binding);
startListener();
}
//When queue is active we start Listener
public void startListener() {
boolean isQueuqReady = false;
while (!isQueuqReady) {
Properties p = admin.getQueueProperties(env.getProperty("management.registry.info.device-type") + "_"
+ env.getProperty("management.registry.info.device-specific-type"));
if (p != null) {
log.info("Rabbit queue is up. Start listener.");
isQueuqReady = true;
registry.getListenerContainer("commandQueue").start();
}
}
}
Problem is that I want, regardless of whether there is a connection or not with the rabbit, the application to be able to work and, accordingly, to intercept when there is a connection and when not to do different actions.

Rabbit MQ connection is getting timed out with Spring Boot

When try to connect Rabbit MQ instance running on seperate host/port , getting this exception that is more related to restarting of consumer and getting timed out
2020-08-22 10:23:25.189 INFO [events-to-slack,,,] 1057 --- [an)#6c74acb-100] o.s.a.r.l.SimpleMessageListenerContainer : Restarting Consumer: tags=[{}], channel=null, acknowledgeMode=AUTO local queue size=0
2020-08-22 10:23:25.203 ERROR [events-to-slack,,,] 1057 --- [an)#6c74acb-110] o.s.a.r.l.SimpleMessageListenerContainer : Failed to check/redeclare auto-delete queue(s).
org.springframework.amqp.AmqpIOException: java.net.SocketTimeoutException: connect timed out
at org.springframework.amqp.rabbit.support.RabbitExceptionTranslator.convertRabbitAccessException(RabbitExceptionTranslator.java:71)
at org.springframework.amqp.rabbit.connection.AbstractConnectionFactory.createBareConnection(AbstractConnectionFactory.java:306)
at org.springframework.amqp.rabbit.connection.CachingConnectionFactory.createConnection(CachingConnectionFactory.java:547)
at org.springframework.amqp.rabbit.core.RabbitTemplate.doExecute(RabbitTemplate.java:1389)
at org.springframework.amqp.rabbit.core.RabbitTemplate.execute(RabbitTemplate.java:1370)
at org.springframework.amqp.rabbit.core.RabbitTemplate.execute(RabbitTemplate.java:1346)
at org.springframework.amqp.rabbit.core.RabbitAdmin.getQueueProperties(RabbitAdmin.java:336)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.redeclareElementsIfNecessary(SimpleMessageListenerContainer.java:1128)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.access$800(SimpleMessageListenerContainer.java:99)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$AsyncMessageProcessingConsumer.run(SimpleMessageListenerContainer.java:1304)
at java.lang.Thread.run(Thread.java:748)
Caused by: java.net.SocketTimeoutException: connect timed out
I'm using spring boot 1.5.18. with Java 1.8
These are my configurations setup for rabbit-mq , here we can see that it's simply using topic exchange and tying to setup a listener with Queue bind with it
#RequiredArgsConstructor
#Configuration
public class RabbitMQConfiguration implements RabbitListenerConfigurer {
private final Config configuration;
private final RetryConfiguration retryConfig;
#Bean
public TopicExchange exchange() {
return new TopicExchange(configuration.getExchange());
}
#Bean
public TopicExchange delayExchange() {
TopicExchange topicExchange = new TopicExchange(configuration.getDelayExchange());
topicExchange.setDelayed(true);
return topicExchange;
}
#Bean
public Queue queue() {
return new Queue(configuration.getQueue());
}
#Bean
public List<Binding> bindings(#Qualifier("exchange") TopicExchange exchange,
#Qualifier("delayExchange") TopicExchange delayExchange,
#Qualifier("queue") Queue queue) {
List<Binding> binding = new ArrayList<>();
for (String routingKey : configuration.getRoutingKeys()) {
binding.add(BindingBuilder.bind(queue).to(exchange).with(routingKey));
binding.add(BindingBuilder.bind(queue)
.to(delayExchange)
.with(retryConfig.getPrefix() + routingKey));
}
return binding;
}
#Bean
public MessageConverter jsonMessageConverter() {
return new Jackson2JsonMessageConverter();
}
#Bean
public MappingJackson2MessageConverter consumerJackson2MessageConverter() {
return new MappingJackson2MessageConverter();
}
#Bean
public DefaultMessageHandlerMethodFactory messageHandlerMethodFactory() {
DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory();
factory.setMessageConverter(consumerJackson2MessageConverter());
return factory;
}
#Override
public void configureRabbitListeners(RabbitListenerEndpointRegistrar endpointRegistrar) {
endpointRegistrar.setMessageHandlerMethodFactory(messageHandlerMethodFactory());
}

How to configure RabbitMQ (in Spring Boot 2.x) so manual acknowledge works

So I read all the examples, I have the com.rabbitmq.client.Channel and the #Header(AmqpHeaders.DELIVERY_TAG), but when I try to call Channel.basicNack(long deliveryTag, boolean multiple, boolean requeue) the result is "java.lang.IllegalStateException: Channel closed; cannot ack/nack". I can see that the CachingConnectionFactory does not support any of the acknowledge methods. So my question is what ConnectionFactory do I have to use and howto configure it, so that basicAck/basicNack works?
Spring Boot Version 2.1.0.RELEASE
application.yaml:
spring:
rabbitmq:
host: ${RABBITMQ_HOST:localhost}
port: ${RABBITMQ_PORT:5672}
username: ${RABBITMQ_USERNAME:guest}
password: ${RABBITMQ_PASSWORD:guest}
listener:
type: simple
simple:
acknowledge-mode: manual
Config class:
#EnableRabbit
#Configuration
public class RabbitMqConfig implements RabbitListenerConfigurer {
#Value("${app.rabbitmq.incoming-queue}")
private String incomingQueue;
private AmqpAdmin amqpAdmin;
#Autowired
public RabbitMqConfig(AmqpAdmin amqpAdmin) {
this.amqpAdmin = amqpAdmin;
}
#Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
rabbitTemplate.setChannelTransacted(true);
rabbitTemplate.setMessageConverter(jsonMessageConverter());
return rabbitTemplate;
}
#Bean
MessageConverter jsonMessageConverter() {
return new Jackson2JsonMessageConverter();
}
#Override
public void configureRabbitListeners(RabbitListenerEndpointRegistrar registar) {
registar.setMessageHandlerMethodFactory(createDefaultMessageHandlerMethodFactory());
}
#Bean
public DefaultMessageHandlerMethodFactory createDefaultMessageHandlerMethodFactory() {
DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory();
factory.setMessageConverter(new MappingJackson2MessageConverter());
return factory;
}
#Bean
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(
ConnectionFactory connectionFactory, CommonAmqpErrorHandler commonAmqpErrorHandler) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
factory.setMessageConverter(jsonMessageConverter());
factory.setErrorHandler(commonAmqpErrorHandler);
return factory;
}
#PostConstruct
public void afterInit() {
amqpAdmin.declareQueue(new Queue(getDeadLetterQueueName(incomingQueue), true));
amqpAdmin.declareQueue(
QueueBuilder.durable(incomingQueue).withArgument("x-dead-letter-exchange", "")
.withArgument("x-dead-letter-routing-key",
getDeadLetterQueueName(incomingQueue)).build());
}
private String getDeadLetterQueueName(String queueName) {
return queueName + ".dead-letter.queue";
}
}
Listener code:
#Transactional(rollbackOn = Exception.class)
#RabbitListener(queues = "${app.rabbitmq.incoming-queue}", errorHandler = "notificationListenerErrorHandler")
public void onMessage(#Valid #Payload NotificationDto notification, Message message,
Channel channel, #Header(AmqpHeaders.DELIVERY_TAG) long tag) throws IOException {
System.out.println("00000 > " + tag);
System.out.println("11111");
channel.basicNack(tag, false, true);
System.out.println("222222");
}
After starting it from scratch, it turns out that the
#Transactional(rollbackOn = Exception.class)
is causing the problem. If I remove it, it's working

Spring boot rabbitmq message not getting requeued

Hi I am trying to requeue certain messages if a specific exception is thrown, but for any validation failures I want them to go straight to the dead letter queue. I have the the relevant queues and dead letter queues enabled. I am finding hat my validation failures are got to the dlq, but the other failures are constantly in an unack state and getting constantly retried, beyond the max-attempts and multiplier I had set up, any ideas why this is? code below I am using Spring boot 2.0.4 release
#RabbitListener(queues = "${queuename}")
public void consume(final #Valid #Payload MyRequest myRequest) {
if (method.fail()) {
throw new RuntimeException("");
}
}
#Bean
public DefaultMessageHandlerMethodFactory myHandlerMethodFactory() {
DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory();
factory.setMessageConverter(jackson2Converter());
factory.setValidator(amqpValidator());
return factory;
}
#Override
public void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) {
registrar.setMessageHandlerMethodFactory(myHandlerMethodFactory());
}
#Bean
public Validator amqpValidator() {
return new OptionalValidatorFactoryBean();
}
#Bean
public Jackson2JsonMessageConverter jackson2JsonMessageConverter() {
return new Jackson2JsonMessageConverter();
}
#Bean
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(ConnectionFactory connectionFactory) {
SimpleRabbitListenerContainerFactory listenerContainerFactory =
new SimpleRabbitListenerContainerFactory();
listenerContainerFactory.setConnectionFactory(connectionFactory());
listenerContainerFactory.setErrorHandler(new ConditionalRejectingErrorHandler(
new MyErrorPayload()));
listenerContainerFactory.setMessageConverter(jackson2JsonMessageConverter());
return listenerContainerFactory;
}
#Bean
public ConnectionFactory connectionFactory() {
CachingConnectionFactory connectionFactory = new CachingConnectionFactory(rabbitQueueHost);
connectionFactory.setUsername(rabbitQueueUsername);
connectionFactory.setPassword(rabbitQueuePassword);
connectionFactory.setVirtualHost(rabbitQueueVirtualHost);
return connectionFactory;
}
public class MyErrorPayload implements FatalExceptionStrategy {
#Override
public boolean isFatal(Throwable t) {
if (t instanceof ListenerExecutionFailedException &&
(t.getCause() instanceof MessageConversionException ||
t.getCause() instanceof MethodArgumentNotValidException )
) {
return true;
}
return false;
}
}
application.yml ( properties)
spring:
rabbitmq:
host: localhost
username: uu
password: pp
virtual-host: /
listener:
simple:
default-requeue-rejected: false
retry:
enabled: true
initial-interval: 2000
multiplier: 1.5
max-interval: 10000
max-attempts: 3
It's because you are not using Boot's auto configuration for the container factory. So the retry configuration is ignored.
#Bean
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(ConnectionFactory connectionFactory) {
SimpleRabbitListenerContainerFactory listenerContainerFactory =
new SimpleRabbitListenerContainerFactory();
listenerContainerFactory.setConnectionFactory(connectionFactory());
listenerContainerFactory.setErrorHandler(new ConditionalRejectingErrorHandler(
new MyErrorPayload()));
listenerContainerFactory.setMessageConverter(jackson2JsonMessageConverter());
return listenerContainerFactory;
}
The same was true for the sample that #Barath references in his comment.
Inject the configurer into your factory method and invoke it; for example, for that sample...
#Bean
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(ConnectionFactory connectionFactory,
SimpleRabbitListenerContainerFactoryConfigurer configurer) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
configurer.configure(factory, connectionFactory);
factory.setErrorHandler(errorHandler());
return factory;
}
If there is only one message converter Bean, the configurer will add that too.
I have updated the sample.
EDIT
Custom retry policy for selective exceptions; the following disables retry for ValidationException but retries all others. (Again, for the sample app)...
#Bean
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(ConnectionFactory connectionFactory,
SimpleRabbitListenerContainerFactoryConfigurer configurer, RabbitProperties properties) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
configurer.configure(factory, connectionFactory);
factory.setErrorHandler(errorHandler());
ListenerRetry retryConfig = properties.getListener().getSimple().getRetry();
if (retryConfig.isEnabled()) {
RetryInterceptorBuilder<?> builder = (retryConfig.isStateless()
? RetryInterceptorBuilder.stateless()
: RetryInterceptorBuilder.stateful());
RetryTemplate retryTemplate = new RetryTemplate();
Map<Class<? extends Throwable>, Boolean> retryableExceptions = Collections
.singletonMap(ValidationException.class, false);
SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(retryConfig.getMaxAttempts(),
retryableExceptions, true, true); // retry all exceptions except Validation
retryTemplate.setRetryPolicy(retryPolicy);
ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
backOffPolicy.setInitialInterval(retryConfig.getInitialInterval().toMillis());
backOffPolicy.setMaxInterval(retryConfig.getMaxInterval().toMillis());
backOffPolicy.setMultiplier(retryConfig.getMultiplier());
retryTemplate.setBackOffPolicy(backOffPolicy);
builder.retryOperations(retryTemplate);
builder.recoverer(new RejectAndDontRequeueRecoverer());
factory.setAdviceChain(builder.build());
}
return factory;
}
No messages are ever requeued since you have default-requeue-rejected: false.

Spring MQTT integration: loss of messages

I'm trying use spring integration to receive a big amount of MQTT messages, process them and then store in a db.
Here is the code:
#Bean
public MqttPahoClientFactory mqttClientFactory() {
DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
factory.setServerURIs("tcp://localhost:1883");
return factory;
}
#Bean
public DefaultPahoMessageConverter messageConverter(){
DefaultPahoMessageConverter converter = new DefaultPahoMessageConverter();
converter.setPayloadAsBytes(true);
return converter;
}
#Bean
public MessageChannel mqttInputChannel() {
return new DirectChannel();
}
#Bean
public MessageProducer mqttInbound() {
MqttPahoMessageDrivenChannelAdapter adapter =
new MqttPahoMessageDrivenChannelAdapter("clientID", mqttClientFactory(), "topic1");
adapter.setCompletionTimeout(5000);
adapter.setConverter(messageConverter());
adapter.setQos(2);
adapter.setOutputChannel(mqttInputChannel());
return adapter;
}
#Bean
#ServiceActivator(inputChannel = "mqttInputChannel")
public MessageHandler handler(){
return new MessageHandler() {
#Override
public void handleMessage(Message<?> arg0) throws MessagingException {
//process messages and storing operations
}
};
}
My problem is that I'm not able to receive all messages and losing some of them, probably because I spend a lot of resources and time inside the handler method. I've tried also to use a QueueChannel instead of the DirectChannel, but when the queue is full the problem remains.
A possible solution could be this, stop the reception till the message is completely handled and then restarts it, but I don't know how. Any advice?
Thanks in advance.

Resources