How to consume message from RabbitMQ dead letter queue one by one - spring-boot

The requirement is like to process the messages from dead letter queue by exposed a REST service API(Spring Boot).
So that once REST service is called, one message will be consumed from the DL queue and will publish in the main queue again for processing.
#RabbitListener(queues = "QUEUE_NAME") consumes the message immediately which is not required as per the scenario. The message only has to be consumed by the REST service API.
Any suggestion or solution?

I do not think RabbitListener will help here.
However you could implement this behaviour manually.
Spring Boot automatically creates RabbitMq connection factory so you could use it. When http call is made just read single message from the queue manually, you could use basic.get to synchronously get just one message:
#Autowire
private ConnectionFactory factory
void readSingleMessage() {
Connection connection = null;
Channel channel = null;
try {
connection = factory.newConnection();
channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
GetResponse response = channel.basicGet(QUEUE_NAME, true);
if (response != null) {
//Do something with the message
}
} finally {
//Check if not null
channel.close();
connection.close();
}
}

If you are using Spring; you can avoid all the boilerplate in the other answer using RabbitTemplate.receive(...).
EDIT
To manually ack/reject the message, use the execute method instead.
template.execute(channel -> {
GetResponse got = channel.basicGet("foo", false);
// ...
channel.basicAck(got.getEnvelope().getDeliveryTag(), false);
return null;
});
It's a bit lower level, but again, most of the boilerplate is taken care of for you.

Related

why jms Client acknowledge is not working

I am trying to retrieve messages from an ActiveMQ queue. I set the session to Session.CLIENT_ACKNOWLEDGE. When I receive the message from the server it is auto-acknowledged although I have not called acknowledge on the message. Below is sample code of what I am doing:
connection = connectionFactory.createConnection();
Session session = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE);
Destination destination = session.createQueue(queue);
consumer = session.createConsumer(destination);
connection.start();
Message message = consumer.receive(1);
The code snippet above works and I do get the message from the queue. The problem is that messages disappear from the queue although I have not acknowledged the message. Any pointers why this is happening. I have been debugging this issue almost all this afternoon with no luck. This is a Spring boot application.
I got the client_ack to work in jmsTemplate. Below is the code.
public Object retrieve() {
return this.jmsTemplate.execute(session -> {
session.recover();
MessageConsumer consumer = session.createConsumer(
this.jmsTemplate.getDestinationResolver().resolveDestinationName(session, queue, false));
try {
Message received = consumer.receive(1);
if (received != null) {
return this.messageConverter.fromMessage(received);
}
}
catch (Exception e) {
return null;
}
finally {
consumer.close();
}
return null;
}, true);
}
The issue I am now seeing is that my app does not see these messages after it restarts. I tried running session.recover() but it is not making any difference. Thanks for your help.
The working code using jmsTemplate is as below:
public Object retrieve() {
return this.jmsTemplate.execute(session -> {
MessageConsumer consumer = session.createConsumer(
this.jmsTemplate.getDestinationResolver().resolveDestinationName(session, queue, false));
try {
Message received = consumer.receive(1);
session.recover();
if (received != null) {
return this.messageConverter.fromMessage(received);
}
}
catch (Exception e) {
return null;
}
finally {
consumer.close();
}
return null;
}, true);
}
This appears to be a problem with the springframework Constants class. It uses introspection to get the integer value of CLIENT_ACKNOWLEDGE but always return a value of 1, so session is always AUTO_ACKNOWLEDGE
To anybody who might run into this issue, I did the following to resolve the issue:
Set the session to non-transacted. This is the default and only option if you are using jmsTemplate. If you are creating your own consumer, you need to create the session as non-transacted when creating the jms connection
If you are creating your own consumer, you need to set the session to CLIENT_ACKNOWLEDGE.
Check the maximum number of redeliveries to the required value. This will differ depending on the product: ActiveMQ, IBM MQ, etc.
call session.recover() after retrieving the message.
I updated the working code that is using jmsTemplate in the original post.

How to re-connect to an existing JMS durable subscriber without knowing the JMS selector

I'm new to JMS and am trying to setup Apache Active MQ for a messaging application as an alternative to Azure Service Bus that I'm very familiar with. I would like to setup topics and durable subscribers as and administrative task, and would like the runtime process to consume messages from those existing durable subscriber only based upon its name and, possibly, client id.
How do I retrieve an existing durable subscriber, without knowing the selector?
All the documentation and the samples I've read show that the only way to consume a message is to call the session.createDurableSubscriber() method.
Additionaly, I prefer to use the AMQP abstraction over JMS. So I found the following code to retrieve an existing subscriber:
public static ReceiverLink RecoverDurableSource(Session session, string topicPath, string subscriptionName)
{
Source recovered = null;
using (var attached = new ManualResetEvent(false))
{
void OnAttached(ILink link, Attach Attach)
{
recovered = (Source)Attach.Source;
attached.Set();
}
ReceiverLink receiver = null;
try
{
receiver = new ReceiverLink(session, subscriptionName, (Source)null, OnAttached);
if (!attached.WaitOne(TimeSpan.FromSeconds(5)))
return null;
CloseReceiverLink(receiver);
return recovered != null
? new ReceiverLink(session, subscriptionName, recovered, null)
: null
;
}
finally
{
if (recovered == null)
CloseReceiverLink(receiver);
}
}
}
private static void CloseReceiverLink(ReceiverLink receiver)
{
if (receiver == null)
return;
if (receiver.Error == null || Equals(receiver.Error.Condition, new Symbol("amqp:not-found")))
receiver.Close();
}
However, this code has the nasty side effect to re-create and default durable subscriber (manifested in this code by the ReceiverLink object) with the same name and then, if it exists, re-creating it with the correct Sourceobject.
But this may disrupt the reception of messages at the time this method is called.

How comes my channel.basicConsume does not wait for messages

Whenever I start the following code:
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
String exchangeName = "direct_logs";
channel.exchangeDeclare(exchangeName, "direct");
String queueName = channel.queueDeclare().getQueue();
channel.queueBind(queueName, exchangeName, "red");
channel.basicQos(1);
final Consumer consumer = new DefaultConsumer(channel){
#Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body) throws IOException{
String message = new String(body, "UTF-8");
System.out.println(message);
System.out.println("message received");
}
};
channel.basicConsume(queueName, true, consumer);
It does not start an endless loop, as is implied in the documentation. Instead, it stops right away.
The only way I can have it consume for some time is to replace channel.basicConsume with a loop, as follows:
DateTime startedAt = new DateTime();
DateTime stopAt = startedAt.plusSeconds(60);
long i=0;
try {
while (stopAt.compareTo(new DateTime()) > 0) {
channel.basicConsume(queueName, true, consumer);
i++;
}
}finally {
System.out.println(new DateTime());
System.out.println(startedAt);
System.out.println(stopAt);
System.out.println(i);
}
There must be a better way to listen to messages for a while, correct? What am I missing?
It stops listening right away.
Are you sure it's stopping? What basicConsume does is register a consumer to listen to a specific queue so there is no need to execute it in a loop. You only execute it once, and the handleDelivery method of the instance of Consumer you pass will be called whenever a message arrives.
The Threads that the rabbitmq library creates should keep the JVM from exiting. In order to exit the program you should actually call connection.close()
Here is a complete receiver example from rabbitmq: https://github.com/rabbitmq/rabbitmq-tutorials/blob/master/java/Recv.java
It's actually pretty much the same as yours.
i had the same issue. the reason was that i was calling connection.close at the end. however, the basicConsume() method does not block on the current thread, rather on other threads, so the code after it, i.e. the connection.close() is called immediately.

Receive method in JMS waiting for messages

I want a method to browse all messages from a messsage queue and can send it to another queue using jmstemplate with using Websphere queues(NOT MQ). I have tried using receive and it is able to retrieve all the messages from the queue but it is still waiting for another message. And the messages are being lost. It must be in a transaction
The Code I have Tried:
**String message = (String) jmsTemplate.receiveAndConvert();
System.out.print(message);
while ((message = (String) jmsTemplate.receiveAndConvert()) != null) {
messages.add(message);
}
return messages;
}**
The JMStemplate should be used for only synchronous read or sending message. For asychronous read use one of the listener implementation. Read here

JMS synchronous Request/Reply using temporary queue timed out reading

I'm currently having a problem with jms synchronous request/reply approach, this is what happens:
1.) ProgramA create a jms message, a temporary queue and set it as a replyTo.
2.) ProgramB has a listener to the message created from ProgramA, process the message and reply to it. But ProgramB needs to communicate to a 3rd party web service that sometimes takes more than 10seconds to reply, and that is the problem I set the consumer to listen for 5000 (5s) and of course it will timeout afterwards. So the message is not received.
My observation:
1.) Even though ProgramA is done reading (no reply yet, at that instant I try to delete the temporary queue). It's not able to and ProgramB was still able to write to the reply queue, but nobody's going to read that message (too late).
When I try to change 5s to 20s listen time the problem was solved, but is it the right approach?
Also is it possible for the ProgramB to not try to write to the queue when ProgramA has stop reading?
Partial codes:
Destination replyQueue = send(jmsUtil, actionDTO);
SalesOrderResponseDTO responseDTO = readReply(jmsUtil, replyQueue, actionDTO);
public Destination send(JmsSessionUtil jmsUtil, SalesOrderActionDTO soDTO) {
try {
utx.begin();
jmsUtil.send(soDTO, null, 0L, 1, Long.parseLong(configBean.getProperty("jms.payrequest.timetolive")), true);
utx.commit();
return jmsUtil.getReplyQueue();
} catch (Exception e) {
try {
utx.rollback();
} catch (Exception e1) {
}
}
return null;
}
public SalesOrderResponseDTO readReply(JmsSessionUtil jmsUtil, Destination replyQueue, SalesOrderActionDTO actionDTO) {
SalesOrderResponseDTO responseDTO = null;
try {
utx.begin();
responseDTO = (SalesOrderResponseDTO) jmsUtil.read(replyQueue);
if (responseDTO != null) {
// fires the response event
SalesOrderResponsePayload eventPayload = new SalesOrderResponsePayload();
eventPayload.setResponseDTO(responseDTO);
responseEvent.fire(eventPayload);
} else { // timeout
((TemporaryQueue) replyQueue).delete();
jmsUtil.dispose();
}
utx.commit();
return responseDTO;
} catch (Exception e) {
try {
utx.rollback();
} catch (Exception e1) {
}
}
return responseDTO;
}
public String send(MessageDTO messageDTO,
JMSQueueEnum resultNotificationQueue, Long parentProcessId,
int JMSPriority, long timeToLive, boolean hasReply)
throws JMSException, InvalidDTOException, NamingException {
try {
// Process optional parameters
messageDTO.setResultNotificationQueue(resultNotificationQueue);
messageDTO.setParentProcessId(parentProcessId);
// Wrap MessageDTO in a JMS ObjectMessage
ObjectMessage msg = MessageDTOHelper.serialize(session, messageDTO);
msg.setJMSType(messageDTO.getClass().getSimpleName());
msg.setStringProperty("DTOType", messageDTO.getClass()
.getSimpleName());
requestProducer = session.createProducer(queue);
if (hasReply) {
replyQueue = session.createTemporaryQueue();
replyConsumer = session.createConsumer(replyQueue);
msg.setJMSReplyTo(replyQueue);
}
if (JMSPriority > -1) {
requestProducer.send(msg, DeliveryMode.PERSISTENT, JMSPriority,
timeToLive);
} else {
// Send the JMS message
requestProducer.send(msg);
}
return msg.getJMSMessageID();
} catch (Exception e) {
}
return null;
}
public MessageDTO read(Destination replyQueue) throws JMSException,
NamingException {
if (replyQueue instanceof Queue) {
Message msg = replyConsumer.receive(20000);
if (msg == null) {
return null;
}
MessageDTO messageDTO = MessageDTOHelper
.deserialize((ObjectMessage) msg);
return messageDTO;
} else {
}
return null;
}
Actual question here is whether you need synchronous or asynchronous communication.
I would always prefer asynchronous, and it seems from your question that there is no need for synchronous communication neither in your case. However, if there is some reason for synchronous then you are stuck with temporary queues - you'll have to specify timeout interval and you'll face problems expressed in your question. If Program A can wait, raise the timeout interval although that's far from optimal. As far as I know, there is no possibility for Program B to check if A still listens.
In case of asynchronous communication, you have (at least) two JMS options:
Using different message queues - Program A sends the message on Queue1 and finishes, but listens (e.g. through Message Driven Bean) on Queue2 where Program B puts its response when it's done. Small drawback is usage of one extra pair of producer and consumer.
Using same message queue - Program A and Program B both send and receive messages on Queue1, but with different message selector (see description here). Basically, message selectors will filter messages for specific listener and thus enable using same queue for bidirectional communication.
See also:
JMS Synchronous Message Consumption
You could have A add a header to its message with the current timestamp + 5 secs. When B receives the response from the 3rd party, if the current time is greater than the header, it should drop the result and not send. You could use the time-to-live jms message property for this, although that is not its express purpose.

Resources