rabbitmq: channel level exception recovery - rabbitmq

I am working on a worker service that consume from rabbitmq queue and I am trying to figure out how to handle channel shut down event, for example: lets say my consumer didn't ack the broker for 30 minutes and the broker shut down the channel for that.
I know the rabbitmq clinet library (I am using the C# library) will automatically try to re-connect on connection shut down, but what is the best practice for when the connection is alive but the channel was closed? I can register handler for the 'Channel Shut Down' event but what should I do inside this handler except for logging it? I want to keep consuming from the the relevant queue after all.
here is my code, i tried to create the channel again but i get timeout exception for that:
var consumer = ...
channel.BasicConsume(queue: queueName, autoAck: false, consumer: consumer);
channel.BasicQos(0, 100, false);
channel.ModelShutdown += (sender, args) =>
{
try
{
Log.Error($"channel was shut down");
channel = _connection.CreateModel();
channel.BasicConsume(queue: queueName, autoAck: false, consumer: consumer);
channel.BasicQos(0, 100, false)
}
catch (Exception exception)
{
Log.Error(exception);
}

As far as I understand, the problem is related to the fact that the event processing occurs in the same thread that is trying to access the RabbitMQ server, or there is a lock blocking the creation of the channel. The simplest solution I've found is to just use Task.Run(() => )
private async void RecreateChannel(object sender, ShutdownEventArgs e)
{
_logger.LogWarning($"Channel was shutdowned, whit reason: {e.ReplyText}, code: {e.ReplyCode}, trying to reconnect");
_channel.Dispose();
while (!_channel.IsOpen)
{
try
{
await Task.Run(() => _channel = InitChannel());
}
catch (Exception exception)
{
_logger.LogError(exception, "Failed to recreate channel, trying again");
}
}
_logger.LogInformation("Channel was recreated successfully");
}

Related

Spring AMQP retry policy - wait with retrying until external system is reachable

Our application is storing data the user enters in SalesForce. We are using Spring AMQP (RabbitMQ) to be able to store data and run the application even if SalesForce is not reachable (scheduled downtimes or else).
So once SalesForce is not available the queue should wait with retrying messages until it is up again. Is this possible using the CircuitBreakerRetryPolicy or do i have to implement a custom RetryPolicy?
Currently i just have a simply retry policy which should be replaced by a complex one that blocks the retries until the external system is reachable again.
#Bean
RetryOperationsInterceptor salesForceInterceptor() {
return RetryInterceptorBuilder.stateless()
.recoverer(new RejectAndDontRequeueRecoverer())
.retryPolicy(simpleRetryPolicy())
.build();
}
Message listener:
#Override
public void onMessage(Message message) {
try {
.....
} catch (ResourceAccessException e) {
//TODO probablyy wrong exception
throw new AmqpTimeoutException("Salesforce can not be accessed, retry later");
} catch (Exception e) {
throw new AmqpRejectAndDontRequeueException("Fatal error with message to salesforce, do not retry", e);
}
}

How to move messages from one queue to another in RabbitMQ

In RabbitMQ,I have a failure queue, in which I have all the failed messages from different Queues. Now I want to give the functionality of 'Retry', so that administrator can again move the failed messages to their respective queue. The idea is something like that:
Above diagram is structure of my failure queue. After click on Retry link, message should move into original queue i.e. queue1, queue2 etc.
If you are looking for a Java code to do this, then you have to simply consume the messages you want to move and publish those messages to the required queue. Just look up on the Tutorials page of rabbitmq if you are unfamiliar with basic consuming and publishing operations.
It's not straight forward consume and publish. RabbitMQ is not designed in that way. it takes into consideration that exchange and queue both could be temporary and can be deleted. This is embedded in the channel to close the connection after single publish.
Assumptions:
- You have a durable queue and exchange for destination ( to send to)
- You have a durable queue for target ( to take from )
Here is the code to do so:
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.QueueingConsumer;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
public object shovelMessage(
String exchange,
String targetQueue,
String destinationQueue,
String host,
Integer port,
String user,
String pass,
int count) throws IOException, TimeoutException, InterruptedException {
if(StringUtils.isEmpty(exchange) || StringUtils.isEmpty(targetQueue) || StringUtils.isEmpty(destinationQueue)) {
return null;
}
CachingConnectionFactory factory = new CachingConnectionFactory();
factory.setHost(StringUtils.isEmpty(host)?internalHost.split(":")[0]:host);
factory.setPort(port>0 ? port: Integer.parseInt(internalPort.split(":")[1]));
factory.setUsername(StringUtils.isEmpty(user)? this.user: user);
factory.setPassword(StringUtils.isEmpty(pass)? this.pass: pass);
Channel tgtChannel = null;
try {
org.springframework.amqp.rabbit.connection.Connection connection = factory.createConnection();
tgtChannel = connection.createChannel(false);
tgtChannel.queueDeclarePassive(targetQueue);
QueueingConsumer consumer = new QueueingConsumer(tgtChannel);
tgtChannel.basicQos(1);
tgtChannel.basicConsume(targetQueue, false, consumer);
for (int i = 0; i < count; i++) {
QueueingConsumer.Delivery msg = consumer.nextDelivery(500);
if(msg == null) {
// if no message found, break from the loop.
break;
}
//Send it to destination Queue
// This repetition is required as channel looses the connection with
//queue after single publish and start throwing queue or exchange not
//found connection.
Channel destChannel = connection.createChannel(false);
try {
destChannel.queueDeclarePassive(destinationQueue);
SerializerMessageConverter serializerMessageConverter = new SerializerMessageConverter();
Message message = new Message(msg.getBody(), new MessageProperties());
Object o = serializerMessageConverter.fromMessage(message);
// for some reason msg.getBody() writes byte array which is read as a byte array // on the consumer end due to which this double conversion.
destChannel.basicPublish(exchange, destinationQueue, null, serializerMessageConverter.toMessage(o, new MessageProperties()).getBody());
tgtChannel.basicAck(msg.getEnvelope().getDeliveryTag(), false);
} catch (Exception ex) {
// Send Nack if not able to publish so that retry is attempted
tgtChannel.basicNack(msg.getEnvelope().getDeliveryTag(), true, true);
log.error("Exception while producing message ", ex);
} finally {
try {
destChannel.close();
} catch (Exception e) {
log.error("Exception while closing destination channel ", e);
}
}
}
} catch (Exception ex) {
log.error("Exception while creating consumer ", ex);
} finally {
try {
tgtChannel.close();
} catch (Exception e) {
log.error("Exception while closing destination channel ", e);
}
}
return null;
}
To requeue a message you can use the receiveAndReply method. The following code will move all messages from the dlq-queue to the queue-queue:
do {
val movedToQueue = rabbitTemplate.receiveAndReply<String, String>(dlq, { it }, "", queue)
} while (movedToQueue)
In the code example above, dlq is the source queue, { it } is the identity function (you could transform the message here), "" is the default exchange and queue is the destination queue.
I also have implemented something like that, so I can move messages from a dlq back to processing. Link: https://github.com/kestraa/rabbit-move-messages
Here is a more generic tool for some administrative/supporting tasks, the management-ui is not capable of.
Link: https://github.com/bkrieger1991/rabbitcli
It also allows you to fetch/move/dump messages from queues even with a filter on message-content or message-headers :)

What is the use case of BrokerService in ActiveMQ and how to use it correctly

I am new about ActiveMQ. I'm trying to study and check how it works by checking the example code provided by Apache at this link:-
http://activemq.apache.org/how-should-i-implement-request-response-with-jms.html
public class Server implements MessageListener {
private static int ackMode;
private static String messageQueueName;
private static String messageBrokerUrl;
private Session session;
private boolean transacted = false;
private MessageProducer replyProducer;
private MessageProtocol messageProtocol;
static {
messageBrokerUrl = "tcp://localhost:61616";
messageQueueName = "client.messages";
ackMode = Session.AUTO_ACKNOWLEDGE;
}
public Server() {
try {
//This message broker is embedded
BrokerService broker = new BrokerService();
broker.setPersistent(false);
broker.setUseJmx(false);
broker.addConnector(messageBrokerUrl);
broker.start();
} catch (Exception e) {
System.out.println("Exception: "+e.getMessage());
//Handle the exception appropriately
}
//Delegating the handling of messages to another class, instantiate it before setting up JMS so it
//is ready to handle messages
this.messageProtocol = new MessageProtocol();
this.setupMessageQueueConsumer();
}
private void setupMessageQueueConsumer() {
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(messageBrokerUrl);
Connection connection;
try {
connection = connectionFactory.createConnection();
connection.start();
this.session = connection.createSession(this.transacted, ackMode);
Destination adminQueue = this.session.createQueue(messageQueueName);
//Setup a message producer to respond to messages from clients, we will get the destination
//to send to from the JMSReplyTo header field from a Message
this.replyProducer = this.session.createProducer(null);
this.replyProducer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
//Set up a consumer to consume messages off of the admin queue
MessageConsumer consumer = this.session.createConsumer(adminQueue);
consumer.setMessageListener(this);
} catch (JMSException e) {
System.out.println("Exception: "+e.getMessage());
}
}
public void onMessage(Message message) {
try {
TextMessage response = this.session.createTextMessage();
if (message instanceof TextMessage) {
TextMessage txtMsg = (TextMessage) message;
String messageText = txtMsg.getText();
response.setText(this.messageProtocol.handleProtocolMessage(messageText));
}
//Set the correlation ID from the received message to be the correlation id of the response message
//this lets the client identify which message this is a response to if it has more than
//one outstanding message to the server
response.setJMSCorrelationID(message.getJMSCorrelationID());
//Send the response to the Destination specified by the JMSReplyTo field of the received message,
//this is presumably a temporary queue created by the client
this.replyProducer.send(message.getJMSReplyTo(), response);
} catch (JMSException e) {
System.out.println("Exception: "+e.getMessage());
}
}
public static void main(String[] args) {
new Server();
}
}
My confusion about the messageBrokerUrl = "tcp://localhost:61616"; You know ActiveMQ service is running on port 61616 by default. Why does this example chooses same port. If I try to run the code thows eception as:
Exception: Failed to bind to server socket: tcp://localhost:61616 due to: java.net.BindException: Address already in use: JVM_Bind
Perhaps if I change the port number, I can execute the code.
Please let me know why it is like this in the example and how to work with BrokerService.
The BrokerService in this example is trying to create an in memory ActiveMQ broker for use in the example. Given the error you are seeing I'd guess you already have an ActiveMQ broker running on the machine that is bound to port 61616 as that's the default port and thus the two are conflicting. You could either stop the external broker and run the example or modify the example to not run the embedded broker and just rely on your external broker instance.
Embedded brokers are great for unit testing or for creating examples that don't require the user to have a broker installed and running.

RabbitMQ 3.5 and Message Priority Queue process is waiting until the task completion

I have some priority based tasks in my application. I have two queues "queue1" and "queue2", i want to gave highest priority to queue1 and lowest priority to queue2. I setup queue1 priority number is 255 and queue2 priority number is 200. I am having an issue in executing the tasks , once priority task is taken , The process is waiting until the task completion synchronously. But as per our project need, this process shouldn't be waiting but just kick off it. How do I achieve it?
I refereed this blog,
Message Received part:
final CountDownLatch latch = new CountDownLatch(3);
ch.basicConsume(QUEUE, true, new DefaultConsumer(ch) {
#Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body) throws IOException {
System.out.println("Received " + new String(body));
latch.countDown();
}
});
latch.await();
I already done this process without priority queue in RabbitMQ https://pamlesleylu.wordpress.com/2013/02/02/hello-world-for-spring-amqp-and-rabbitmq/
Producer
public void execute() {
System.out.println("execute...");
messageQueue.convertAndSend("hello " + counter.incrementAndGet());
}
Consumer
public void onMessage(Message msg) {
System.out.println(new String(msg.getBody()));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
You need to hand the task off to another thread (e.g. using a TaskExecutor).
Bear in mind, though, that the message will be acked so if the server crashes you will lose that message.
It would probably be better to increase the concurrency in the listener container so you can handle multiple messages at once.

Using exclusive + durable queues, for RabbitMQ

If I have made a queue which is exclusive and durable (not auto-delete). Now, if the consumer subscribes to that queue and then it goes down. Then that queue gets deleted.
I have checked the scenario, when the queue is only durable (i.e. neither exclusive nor auto-delete). Now, if the consumer subscribes to that queue and then it goes down. Then that queue gets deleted.
Please explain the 1st case, 2nd case is giving expected result. In both the scenario only 1 consumer is subscribed to one queue, and there is only one queue bound to one direct_exchange.
If you have a queue that is exclusive, then when the channel that declared the queue is closed, the queue is deleted.
If you have a queue that is auto-deleted, then when there are no subscriptions left on that queue it will be deleted.
These two rules apply even for durable queues.
One thing to correct, the exclusive queue will be deleted after the connection is closed not the channel is closed. you can run this test:
package rabbitmq.java.sample.exclusivequeue;
import java.io.IOException;
import com.rabbitmq.client.*;
import com.rabbitmq.client.AMQP.Queue.DeclareOk;
public class Producer {
private final static String QUEUE_NAME = "UserLogin2";
private final static String EXCHANGE_NAME = "user.login";
/**
* #param args
*/
public static void main(String[] args) {
ConnectionFactory factory=new ConnectionFactory();
factory.setHost("CNCDS108");
try {
Connection conn = factory.newConnection();
Channel channel =conn.createChannel();
DeclareOk declareOk = channel.queueDeclare(QUEUE_NAME, false, true, false, null);
channel.basicPublish("", QUEUE_NAME, null, "Hello".getBytes());
//close the channel, check if the queue is deleted
System.out.println("Try to close channel");
channel.close();
System.out.println("Channel closed");
System.out.println("Create a new channel");
Channel channel2 =conn.createChannel();
DeclareOk declareOk2 = channel2.queueDeclarePassive(QUEUE_NAME);
**//we can access the exclusive queue from another channel
System.out.println(declareOk2.getQueue()); //will output "UserLogin2"
channel2.basicPublish("", QUEUE_NAME, null, "Hello2".getBytes());
System.out.println("Message published through the new channel");**
// System.out.println("Try to close Connection");
// conn.close();
// System.out.println("Connection closed");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}