Our application is using org.springframework.cloud, spring-cloud-starter-stream-rabbit framework and we are trying to avoid sending specific messages to DLQ and also retrying them, this behaviour should be somehow dynamically, because, for the default messages, retries and DLQ should work.
According to this documentation:
Putting it All Together
And this useful post:
DLX in rabbitmq and spring-rabbitmq - some considerations of rejecting messages
It seems that ImmediateAcknowledgeAmqpException could be used in spring AMQP to mark a message as acknowledged and no further process it. However, when we use this code:
#StreamListener(LogSink.INPUT)
public void handle(Message<Map<String, Object>> message) {
if (message.getPayload().get("condition1").equals("abort")) {
throw new ImmediateAcknowledgeAmqpException("error, we don't want to send this message to DLQ");
}
...
}
The message is always send to DLQ
Our current configuration:
spring.cloud.stream:
bindings:
log:
consumer.concurrency: 10
destination: log
group: myGroup
content-type: application/json
rabbit.bindings:
log:
consumer:
autoBindDlq: true
republishToDlq: true
transacted: true
Are we missing something? Is there any other alternative to avoid publishing to DLQ and requeuing?
republishToDlq does not look at that exception; it only applies when the exception is thrown to the container (method causeChainHasImmediateAcknowledgeAmqpException()).
Republishing subverts that logic since no exception is thrown to the container.
Please open an issue against the Rabbit binder, republishToDlq should honor that exception and discard the failed message.
Related
Question
Is it possible to further specifiy what kind of failures trip a dapr circuit breaker targeting a component (AWS SQS)?
Use case
I am sending emails via AWS SES. If you reach your sending limits, the AWS sdk throws an error (code 454). In this case, I want a circuit breaker to stop the processing of the queue and retry sending the emails later.
However, when you have another error, e.g. invalid email address, I don't want this to trip the circuit breaker as it is not transient. I would like to send the message to the DLQ though, as to manually examine these messages later (-> that's why I am still throwing here and not failing siltently).
Simplified setup
I have a circuit breaker defined that trips when my snssqs-pubsub component has more than 3 consecutive failures:
circuitBreakers:
pubsubCB:
# Available variables: requests, totalSuccesses, totalFailures, consecutiveSuccesses, consecutiveFailures
trip: consecutiveFailures > 3
# ...
targets:
components:
snssqs-pubsub-emails:
inbound:
circuitBreaker: pubsubCB
In my application, I want to retry sending emails that failed because the AWS SES sending limit was hit:
try {
await this.sendMail(options);
} catch (error) {
if (error.responseCode === '454') {
// This error should trip the cicuit breaker
throw new Error({ status: 429, 'Rate limited. Should be retried.' })
} else {
// This error should not trip the circuit breaker.
// Because status is 404, dapr directly puts the message into DLQ and skips retry
throw new NotFoundError({ status: 404 })
}
}
You may not have a problem to worry about if you have business case that does not violate AWS Terms of Service. You can put a support ticket and get SES Service Limit's raised.
It does not appear that dapr retry policies don't support the customization you need but .NET does.
If you don't want to process the message, then don't delete it. You can then set visibility timeout of the message in the SQS so they stay hidden to avoid processing again too quickly. Any exception thrown regardless will end up in the DLQ.
I'm using spring-cloud-aws to send a message to SQS FIFO queue.
It's failing with
The request must contain the parameter MessageGroupId
There doesn't seem to be anywhere on the QueueMessagingTemplate in spring-cloud-aws-messaging that allows me to set this mandatory MessageGroupId.
Is there currently a way of writing to a SQS FIFO queue in this manor or would I have to revert to directly using amazons API?
Spring Cloud AWS support FIFO queues since 2017, in accordance with: Add Support for FIFO SQS Queues #252
You just need to add the two required params(messageGroupId and messageDeduplicationId) like example below:
public void send(String topicName, Object message, String messageGroupId, String messageDeduplicationId) throws MessagingException {
Map<String, Object> headers = new HashMap<>();
headers.put("message-group-id", messageGroupId);
headers.put("message-deduplication-id", messageDeduplicationId);
messagingTemplate.convertAndSend(topicName, message, headers);
}
I dont believe that FIFO support is possible with versions 1.1.x of spring-cloud-aws due to how the QueueMessagingTemplate uses a QueueMessagingChannel that does not support configuring the SendMessageRequest in this way.
Examine https://github.com/spring-cloud/spring-cloud-aws/blob/master/spring-cloud-aws-messaging/src/main/java/org/springframework/cloud/aws/messaging/core/QueueMessageChannel.java#L78 for details.
I have opened https://github.com/spring-cloud/spring-cloud-aws/issues/246 for this reason, though have no idea if support will be added.
It also does not appear that I can use a custom QueueMessageTemplate; this would be a reasonable workaround if I could.
I have created on replyQ and done biniding with one direct exchange.
Created the message by setting replyto property to "replyQ"
And sending the message on rabbit to the other service.
The service at other end getting the message and sending reply on given replyTo queue.
and now I am trying to read from a replyQ queue using
template.receiveAndConvert(replyQueue));
But getting null response and i can see the message in the replyQ.
That is the service is able to send the reply but am not able to read it from the given queue
Please help what is going wrong.
template.receiveAndConvert() is sync, blocked for some time one time function, where default timeout is:
private static final long DEFAULT_REPLY_TIMEOUT = 5000;
Maybe this one is your problem.
Consider to switch to ListenerContainer for continuous queue polling.
Another option is RabbitTemplate.sendAndReceive(), but yeah, with fixed reply queue you still get deal with ListenerContainer. See Spring AMQP Reference Manual for more info.
I don't know if this could help anyone, but I found out that declaring the expected Object as a parameter of a method listener did the work
#RabbitListener(queues = QUEUE_PRODUCT_NEW)
public void onNewProductListener(ProductDTO productDTO) {
// messagingTemplate.receiveAndConvert(QUEUE_PRODUCT_NEW) this returns null
log.info("A new product was created {}", productDTO);
}
I have two confusions.
1.If RuntimeException thrown from message listener, does SimpleMessageListenrContainer stop?
2.If SimpleMessageListenerContainer not stop, what is behavior about the auto acknowledgement?
Currently, I want that if the message listener handle message failed, i just log the error and don't stop the container meanwhile notify the broker have received message.
Now I just catch the throwable in message listener method, is it the right way?
No, the container won't stop.
If the listener throws an exception, the message is rejected (and requeued by default). You can change the default behavior to discard the message by setting defaultRequeueRejected to false (it's true by default). Or throw an AmqpRejectAndDontRequeueException which instructs the container to reject (and not requeue) the message - the ack is sent as if the listener had thrown no exception.
When a message is rejected without requeuing, it is either discarded or sent to the dead letter exchange, if the queue is so configured.
Inside MessageListener I throw exception. Default behaviour of message listener container is requeue message. My question - is it possible to change message headers when message was requeued?
No, you cannot change a rejected message; you would have to publish a new message after the exception instead of rejecting it.
The framework provides a mechanism to do that using a RepublishMessageRecoverer, adding headers including the stack trace, but it doesn't let you add your own headers; you would need to subclass it to do that.