I have a Spring JMS Consumer class that reads messages off a queue (implements SessionAwareMessageListener) and processes them for sending off to a web service. We need to preserve the order in which the messages arrive and are processed, as they contain incremental updates to the same data.
To ensure this, we roll back the Session in the listener in case of any recoverable failure, such as a service timeout, so the same message can be retried again. However, if the message has an invalid format or contains bad data, it is discarded (session is not rolled back).
In case of a JMSException, which is thrown by the message.getText() method, I am not clear if I should roll back the session or not. Can this be considered a recoverable error or should the message be discarded in case this error occurs?
The code looks something like this:
public void onMessage(Message message, Session session) throws JMSException {
try {
String msgText = ((TextMessage) message).getText();
// Processing occurs
// Web service is called
} catch (JMSException jmse) {
// log error
session.rollback(); // Question about this line
} catch (InvalidMessageException ime) {
// log error
// session is NOT rolled back, proceed to next message
} catch (SocketTimeoutException | AnyOtherRecoverableException excp) {
// log error
session.rollback();
}
}
In order to preserve ordering (sequencing), you have to roll back the message, if there are any exceptions because of the JMS provider (MQ server) failures.
Also please find the below text from oracle doc on getText() method:
TextMessage getText() method Throws:
JMSException - if the JMS provider fails to get the text due to some internal error
Related
I am using org.springframework.boot:spring-boot-starter-amqp:2.6.6 .
According to the documentation, I set up #RabbitListener - I use SimpleRabbitListenerContainerFactory and the configuration looks like this:
#Bean
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(ObjectMapper om) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory());
factory.setAcknowledgeMode(AcknowledgeMode.MANUAL);
factory.setConcurrentConsumers(rabbitProperties.getUpdater().getConcurrentConsumers());
factory.setMaxConcurrentConsumers(rabbitProperties.getUpdater().getMaxConcurrentConsumers());
factory.setMessageConverter(new Jackson2JsonMessageConverter(om));
factory.setAutoStartup(rabbitProperties.getUpdater().getAutoStartup());
factory.setDefaultRequeueRejected(false);
return factory;
}
The logic of the service is to receive messages from rabbitmq, contact an external service via the rest API (using rest template) and put some information into the database based on the results of the response (using spring data jpa). The service implemented it successfully, but during testing it ran into problems that if any exceptions occur during the work of those thrown up the stack, the message is not sent to the configured dlq, but simply hangs in the broker as unacked. Can you please tell me how you can tell spring amqp that if any error occurs, you need to redirect the message to dlq?
The listener itself looks something like this:
#RabbitListener(
queues = {"${rabbit.updater.consuming.queue.name}"},
containerFactory = "rabbitListenerContainerFactory"
)
#Override
public void listen(
#Valid #Payload MessageDTO message,
Channel channel,
#Header(AmqpHeaders.DELIVERY_TAG) Long deliveryTag
) {
log.debug(DebugMessagesConstants.RECEIVED_MESSAGE_FROM_QUEUE, message, deliveryTag);
messageUpdater.process(message);
channel.basicAck(deliveryTag, false);
log.debug(DebugMessagesConstants.PROCESSED_MESSAGE_FROM_QUEUE, message, deliveryTag);
}
In rabbit managment it look something like this:
enter image description here
and unacked will hang until the queue consuming application stops
See error handling documentation: https://docs.spring.io/spring-amqp/docs/current/reference/html/#annotation-error-handling.
So, you just don't do an AcknowledgeMode.MANUAL and rely on the Dead Letter Exchange configuration for those messages which are rejected in case of error.
Or try to use a this.channel.basicNack(deliveryTag, false, false) in case of messageUpdater.process(message); exception...
Let's take the following consumer method for a RabbitMQ queue. Ths payload received from the queue is in JSON format, so I register a bean returning a Jackson2JsonMessageConverter. This basically works fine.
Now I'd like to add a validation of the QueueResponse object, similar to when using Jackson in a #RestController, e.g. if the JSON field does not exist or contains an invalid value. In this case, I'd like the code to execute the catch block, i.e. throwing an AmqpRejectAndDontRequeueException.
Thus, I added #Payload #Valid as described in the documentation. But I don't know what to do in the validationErrorHandler method. I don't understand the return statement from the documentation. What would I need to do there to reach the catch block?
#RabbitListener(queues = QUEUE_NAME, messageConverter = "jackson2MessageConverter", errorHandler="validationErrorHandler")
public void consume(#Payload #Valid QueueResponse queueResponse) {
try {
processMessage(queueResponse);
} catch (Exception e) {
throw new AmqpRejectAndDontRequeueException(e.getMessage());
}
}
#Bean
public MessageConverter jackson2MessageConverter() {
return new Jackson2JsonMessageConverter(objectMapper);
}
// Not sure what to do here...
#Bean
public RabbitListenerErrorHandler validationErrorHandler() {
return (m, e) -> {
...
};
}
If the error handler exits normally, the message will be acknowledged (discarded).
If the error handler throws an exception, the message will either be requeued (and redelivered) or discarded (and optionally sent to a dead letter queue), depending on the exception type, container properties, and queue arguments.
Basically what I do in the RabbitListenerErrorHandler is the following:
check how many times I requeued a message by looking into the count property in the x-death header
then decide to either requeue the message by throwing an AmqpRejectAndDontRequeueException or not. In this last case, instead of just discard the message, send it to a parking-lot exchange (bound to a queue with no consumer, in my case these queues are monitored externally) with additional information (for instance the stack trace of the last failure)
In grpc server interceptor, I want to catch the exceptions and send the full stack trace to the client.
public void onHalfClose() {
try {
super.onHalfClose();
} catch (Exception ex) {
handleException(ex);
throw ex;
}
}
private void handleException(Exception exception) {
var status = Status.fromThrowable(exception);
// status.cause has the full stack trace now
// Add requestHeaders with new metadata.
call.close(status, requestHeaders);
}
On the client-side, I have a ClientInterceptor, which intercepts the OnClose()
#Override
public void onClose(Status status, Metadata requestHeaders) {
// status is different, requestHeaders include the added info
super.onClose(status, requestHeaders);
}
I am able to receive the headers, but not the status. I could copy the entire stack trace as a string in one of the headers, but its has problems of header size limitations of 8K, which is not enough for extra long stack traces I get in my application.
The Status's cause is dropped and not serialized and sent to the client. Only the Status code and its description are sent. So you will never receive the stacktrace.
You can make the augment the Status description with the stacktrace string (Status.augmentDescription) if you want to preserve it and have it sent to the client side.
But anyways, all the Status information is serialized in the response trailer, which needs to conform the 8 KB size limit. No matter where the stacktrace is embedded.
The default implementation of onMessage in AbstractAdaptableMessageListener does simply:
onMessage(Message message) {
try {
onMessage(message, null);
} catch(Throwable ex) {
handleListenerException(ex);
}
}
Even though the attempt at handling exception is commendable the actual handling in handleListenerException is not sufficient in most cases (it just logs an error level message about the exception). Most real application who would love to make use of the MessageListenerAdapter and simply create a POJO to receive the formatted message have much more granular exception handling requirements.
Why was it done like this and could the onMessage implementation simply be replaced with a call to the onMessage method with a null Session argument and no try catch and simply allow the application to intercept (Java Enterprise interceptor or Spring Aspect) the onMessage JMS method and perform integrated/coherent exception handling? In the past, I have had to subclass MessageListenerAdapter simply to override either the onMessage or handleListenerException method to handle exceptions myself.
I am using Spring DefaultMessageListenerContainer and JMS Message Listener to consume messages from Solace Queue. Client Acknowledgement is set to true.
In case of exception messages remain in the queue since they were not acknowledged and these are not redelivered. New messages that were pumped post exception are processed.
Have read about using session.recover but how do we get handle to session . Also tried setting maxredelivery to 3 . But not working.
public void onMessage(Message message) {
String text = null;
ALNTLogger.trace(CLAZZ_NAME, "onMessage()", "Message Received:");
try {
TextMessage textMessage = (TextMessage) message;
text = textMessage.getText();
ALNTLogger.trace(CLAZZ_NAME, "onMessage()", "Message Received: " + text);
Document xmlDocument = parseXml(text);
Map < String, String > values = getValues(xmlDocument);
saveValues(values);
message.acknowledge();
} catch (Exception ex) {
ALNTLogger.error(CLAZZ_NAME, "onMessage()", "Failed to process message:" + text);
throw new RuntimeException(ex);
}
}
Any help will be appreciated
It is expected that the message is not redelivered when using CLIENT_ACKNOWLEDGE acknowledgement mode with a DefaultMessageListenerContainer.
The Spring documentation states the following:
The listener container offers the following message acknowledgment
options:
"sessionAcknowledgeMode" set to "AUTO_ACKNOWLEDGE" (default):
Automatic message acknowledgment before listener execution; no
redelivery in case of exception thrown.
"sessionAcknowledgeMode" set
to "CLIENT_ACKNOWLEDGE": Automatic message acknowledgment after
successful listener execution; no redelivery in case of exception
thrown.
"sessionAcknowledgeMode" set to "DUPS_OK_ACKNOWLEDGE": Lazy
message acknowledgment during or after listener execution; potential
redelivery in case of exception thrown.
"sessionTransacted" set to
"true": Transactional acknowledgment after successful listener
execution; guaranteed redelivery in case of exception thrown.
You can use the last option, transactional acknowledgements, in order to have the message redelivered when the onMessage() method does not return normally.