MQ JMS setClientReconnectOptions doesn't work as expected? - jms

I have a simple code to put 2 messages into a queue.
1) I set the connectionNameList with two servers.
2) Those two servers are independent, but have the same Queue Manager and Queue defined with same name, such as "QMgr" and "TEST.IN"
3) I set the setClientReconnectOptions(WMQConstants.WMQ_CLIENT_RECONNECT);
I hope when the first server is down, it should send the messages to 2nd one.
The test I did:
a) I send first message, sender.send(message); It worked.
b) sleep 30 seconds.
During this time, I shutdown the first server
c) then sleep done, try to send 2nd message, but it failed to send immediately
Further more, I tried more, I did try{} catch{} for 2nd message, and in the catch{}, I try to sender.send(message), it still fails.
Any idea why it is different than what I expected. I will really appreciate your reply.
public static void main(String[] args) throws Exception
{
MQQueueConnectionFactory cf = new MQQueueConnectionFactory();
cf.setConnectionNameList("10.230.34.191(1418),10.230.34.169(1418)");
cf.setQueueManager("QMgr");
cf.setTransportType(WMQConstants.WMQ_CM_CLIENT);
cf.setClientReconnectOptions(WMQConstants.WMQ_CLIENT_RECONNECT);
cf.setClientReconnectTimeout(600);
System.out.println("connect list " + cf.getConnectionNameList());
MQQueueConnection connection = (MQQueueConnection) cf
.createQueueConnection("mqm", "passwd");
MQQueueSession session = (MQQueueSession) connection.createQueueSession(false,
Session.AUTO_ACKNOWLEDGE);
MQQueue queue = (MQQueue) session.createQueue("queue:///TEST.IN");
MQQueueSender sender = (MQQueueSender) session.createSender(queue);
long uniqueNumber = System.currentTimeMillis() % 1000;
JMSTextMessage message = (JMSTextMessage) session.createTextMessage("SimplePTP "
+ uniqueNumber);
// Start the connection
connection.start();
sender.send(message);
System.out.println("Sent message:\\n" + message);
System.out.println("sleep 30 seconds");
Thread.sleep(30000);
uniqueNumber = System.currentTimeMillis() % 1000;
message = (JMSTextMessage) session.createTextMessage("SimplePTP " + uniqueNumber);
sender.send(message);
sender.close();
session.close();
connection.close();
System.out.println("\\nSUCCESS\\n");
}

Well this is the simplest test case and should have worked. How did you bring down the first queue manager? Did you down it with a -r option. Remember, without the -r option the clients will not reconnect when queue manager is ended with endmqm command.
endmqm -r <qm name>
Assuming you used -r option and it still did not work, then my suggestion would be to try the following:
Set an exception listener to know what is going on with reconnection. Exception listener would be invoked when the connection is broken and reconnection attempt starts till either reconnection is successful or fails. Exception listener sample code would be something like this:
conn.setExceptionListener(new ExceptionListener() {
public void onException(JMSException e) {
System.out.print(e);
}
});

Related

Virtual Destinations Active and client id wildcard

I have been reading the documentation for virtual destinations here: http://activemq.apache.org/virtual-destinations.html
But I hit a bit of a snag, when I send to a topic it does not seem to follow the client id name as described on the document
My setup on the active mq is:
<destinationInterceptors>
<virtualDestinationInterceptor>
<virtualDestinations>
<virtualTopic name="Destination.>" prefix="Target.*." selectorAware="false" />
</virtualDestinations>
</virtualDestinationInterceptor>
</destinationInterceptors>
The code above describes that when I send to a Destination.Status topic with a ClientId of CustomerA.
It should send only to Target.CustomerA.Destination.Status if understand correctly, but what's happening is it's sending to Target.CustomerA.Destination.Status and Target.CustomerB.Destination.Status so basically fanning out messages to queues and ignoring the client id.
I did not see any further documentation about how to configure it, i was wondering if anyone else encountered this ?
Am I missing something here ?
Below is my producer if it's helpful.
public static class HelloWorldProducer implements Runnable {
public void run() {
try {
// Create a ConnectionFactory
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://localhost:61617");
// Create a Connection
Connection connection = connectionFactory.createConnection();
connection.setClientID("CustomerA");
connection.start();
// Create a Session
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
// Create the destination (Topic or Queue)
Destination destination = session.createTopic("Destination.Status");
// Create a MessageProducer from the Session to the Topic or Queue
MessageProducer producer = session.createProducer(destination);
producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
// Create a messages
String text = "Hello world! From: " + Thread.currentThread().getName() + " : " + this.hashCode();
TextMessage message = session.createTextMessage(text);
// Tell the producer to send the message
System.out.println("Sent message: "+ message.hashCode() + " : " + Thread.currentThread().getName());
producer.send(message);
// Clean up
session.close();
connection.close();
}
catch (Exception e) {
System.out.println("Caught: " + e);
e.printStackTrace();
}
}
}
Any inputs will be beneficial.
The sender in this scenario has no real effect on the routing at the broker whether or not you've set a ClientID as it is just sending to a named Topic, in this case "Destination.Status". The configuration on the broker controls the routing and in your case you've configured "Destination.>" so any Queue consumer that comes along and subscribes to a Queue that matches the configuration you've set. So in your case I'd guess you have one consumer subscribing to Queue (Target.CustomerA.Destination.Status) and one to Queue (Target.CustomerB.Destination.Status) which then causes any message sent to the Topic to be fanned out to both.
If you want competing consumers then you'd need to subscribe both to Target.CustomerA.Destination.Status and then the broker would round-robin dispatch the sent message to either of the active subscribers.

ActiveMQ how to resend/retry DLQ messages programmatically

I would like to create a simple code snippet that fetches all messages from the DLQ and re-sends them to the original destination (AKA resend/retry)
It can be done easily by the ActiveMQ UI (but for a single message at a time).
There is no direct JMS API for re-sending a message from a DLQ to its original queue. In fact, the JMS API doesn't even discuss dead-letter queues. It's merely a convention used by most brokers to deal with messages that can't be consumed.
You'd need to create an actual JMS consumer to receive the message from the DLQ and then create a JMS producer to send the message back to its original queue.
It's important that you use Session.TRANSACTED mode to avoid potential message loss or duplication.
If you use Session.AUTO_ACKNOWLEDGE and there is a problem between the time the message is consumed and sent (e.g the application crashes, hardware failure, etc.) then the message could be lost due to the fact that it was already acknowledged before it was sent successfully.
If you use Session.CLIENT_ACKNOWLEDGE and there is a problem between the time the message is sent and acknowledged then the message could ultimately be duplicated due to the fact that it was already sent before it was acknowledged successfully.
Both operations should be part of the JMS transaction so that the work is atomic.
Lastly, I recommend you either invoke commit() on the transacted session for each message sent or after a small batch of messages (e.g. 10). Given that you have no idea how many messages are in the DLQ it would be unwise to process every message in a single transaction. Generally you want the transaction to be as small as possible in order to minimize the window during which an error might occur and the transaction's work will need to be performed again. Also, the larger the transaction is the more heap memory will be required on the broker to keep track of the work in the transaction. Keep in mind that you can invoke commit() on the same session as many times as you want. You don't need to create a new session for each transaction.
Retrying all messages on the DLQ is already implemented in activemq as an mbean.
You can trigger the retry method with jmxterm/jolokia
e.g
Replaying all messages on queue ActiveMQ.DLQ with jolokia
curl -XGET --user admin:admin --header "Origin: http://localhost" http://localhost:8161/api/jolokia/exec/org.apache.activemq:brokerName=localhost,destinationName=ActiveMQ.DLQ,destinationType=Queue,type=Broker/retryMessages
NOTE: You can only use this method on a queue that is marked as a DLQ. It will not work for regular queues.
Also the DLQ queue can have its 'DLQ' flag set to false if the server is restarted. It is automatically set to true when a new message is sent to the DLQ
After Justin's reply I've manually implemented the retry mechanism like so:
public void retryAllDlqMessages() throws JMSException {
logger.warn("retryAllDlqMessages starting");
logger.warn("Creating a connection to {}", activemqUrl);
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory("test", "test", activemqUrl);
HashMap<String, MessageProducer> messageProducersMap = new HashMap<>();
MessageConsumer consumer = null;
try (ActiveMQConnection connection = (ActiveMQConnection) connectionFactory.createConnection();
ActiveMQSession session = (ActiveMQSession) connection.createSession(true, Session.SESSION_TRANSACTED)) {
String dlqName = getDlqName();
logger.warn("Creating a session to {}", dlqName);
ActiveMQQueue queue = (ActiveMQQueue) session.createQueue(dlqName);
logger.warn("Starting JMS Connection");
connection.start();
logger.warn("Creating a DLQ consumer");
consumer = session.createConsumer(queue);
logger.warn("Consumer start receiving");
Message message = consumer.receive(CONSUMER_RECEIVE_TIME_IN_MS);
int retriedMessages = 0;
while (message != null) {
try {
retryMessage(messageProducersMap, session, message);
retriedMessages++;
} catch (Exception e) {
logger.error("Error calling retryMessage for message = {}", message);
logger.error("Rolling back the JMS transaction...");
session.rollback();
return;
}
message = consumer.receive(CONSUMER_RECEIVE_TIME_IN_MS);
}
logger.warn("Consumer finished retrying {} messages", retriedMessages);
logger.warn("Commiting JMS Transactions of retry");
session.commit();
} finally {
if (!messageProducersMap.isEmpty()) {
logger.warn("Closing {} messageProducers in messageProducersMap", messageProducersMap.size());
for (MessageProducer producer : messageProducersMap.values()) {
producer.close();
}
}
if (consumer != null) {
logger.warn("Closing DLQ Consumer");
consumer.close();
}
}
}
private void retryMessage(HashMap<String, MessageProducer> messageProducersMap, ActiveMQSession session, Message message) {
ActiveMQObjectMessage qm = (ActiveMQObjectMessage) message;
String originalDestinationName = qm.getOriginalDestination().getQualifiedName();
logger.warn("Retry message with JmsID={} to original destination {}", qm.getJMSMessageID(), originalDestinationName);
try {
if (!messageProducersMap.containsKey(originalDestinationName)) {
logger.warn("Creating a new producer for original destination: {}", originalDestinationName);
messageProducersMap.put(originalDestinationName, session.createProducer(qm.getOriginalDestination()));
}
logger.info("Producing message to original destination");
messageProducersMap.get(originalDestinationName).send(qm);
logger.info("Message sent");
} catch (Exception e) {
logger.error("Message retry failed with exception", e);
}
}

Reusing JMSContext in IBM MQ

I'm trying to reuse a JMSContext to send multiple messages using the same context as shown in this IBM MQ tutorial.
context = cf.createContext();
destination = context.createQueue(QUEUE_NAME);
producer = context.createProducer();
for (int i = 1; i <= 5000; i++) {
try {
TextMessage message = context.createTextMessage("Message " + i + ".\n");
producer.send(destination, message);
} catch (Exception ignore) {}
}
context.close();
Say the connection is dropped at some point. Will the context auto recovers or will I need to reconstruct the context again?
UPDATE --
This is how the current connection factory is being constructed:
JmsFactoryFactory ff = JmsFactoryFactory.getInstance(JmsConstants.WMQ_PROVIDER);
JmsConnectionFactory cf = ff.createConnectionFactory();
cf.setStringProperty (CommonConstants.WMQ_HOST_NAME, config.getHost());
cf.setIntProperty (CommonConstants.WMQ_PORT, config.getPort());
cf.setStringProperty (CommonConstants.WMQ_CHANNEL, config.getChannel());
cf.setIntProperty (CommonConstants.WMQ_CONNECTION_MODE, CommonConstants.WMQ_CM_CLIENT);
cf.setStringProperty (CommonConstants.WMQ_QUEUE_MANAGER, config.getQueueManager());
cf.setBooleanProperty (JmsConstants.USER_AUTHENTICATION_MQCSP, false);
cf.setIntProperty (JmsConstants.PRIORITY, 0);
return cf.createContext();
Reconnect works like this (see also comment of #JoshMc):
On the client, set the reconnect option like this:
cf.setIntProperty(CommonConstants.WMQ_CLIENT_RECONNECT_OPTIONS, CommonConstants.WMQConstants.WMQ_CLIENT_RECONNECT);
On the server, stop the queue manager like this:
endmqm -r
Have u tried with creating JMSContext from existing one?
JMSContext#createContext(int sessionMode)
It will create new JMSContext but reuse the same connection.
Reference:
https://www.ibm.com/support/knowledgecenter/en/SSFKSJ_9.1.0/com.ibm.mq.pro.doc/intro_jms_model.htm
https://docs.oracle.com/javaee/7/api/javax/jms/JMSContext.html

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.

JMS QueueReceiver - Need to keep receiver wait for sometime even messages are available on Queue

I have to update my existing JMS Receiver program to as follows.
Existing Functionality:
My receiver class will read a message and calls a web service to process the job in one of the server once the message is received as xml.
New Functionality:
The receiver should wait for sometime until the job server is free to process a job. I tried using MessageSelectors but which is only applicable for message headers.I tried this option "message = (JMSTextMessage) mqQueueReceiver.receive(100000000000000);" but whenever i posted a message those message is read after posted into queue. But i want to keep receiver to wait for some interval which i am getting from Job server through web service call.
My Code is below:
connectionFactory = new MQQueueConnectionFactory();
connectionFactory.setHostName(config.getValue("host"));
connectionFactory.setPort(Integer.parseInt(config.getValue("port")));
connectionFactory.setTransportType(JMSC.MQJMS_TP_CLIENT_MQ_TCPIP);
connectionFactory.setQueueManager(config.getValue("manager"));
connectionFactory.setChannel(config.getValue("channel"));
queueConnection = (MQQueueConnection) connectionFactory.createQueueConnection();
queueSession = (MQQueueSession) queueConnection.createQueueSession(true, Session.CLIENT_ACKNOWLEDGE);
queue = (MQQueue) queueSession.createQueue(config.getValue("queue"));
mqQueueReceiver = (MQQueueReceiver) queueSession.createReceiver(queue);
while(true) {
if(this.stopListener) {
System.out.println("stopListener variable is changed ");
break;
}
try {
message = (JMSTextMessage) mqQueueReceiver.receive(1000);
String response = "";
if(this.nullCheckJMSTextObject(message)) {
response= soapClient.invokeWebService(message.getText(),message.getJMSCorrelationID());
if(this.nullCheckSoapResponse(response)) {
queueSession.commit();
} else {
queueSession.rollback();
queueSession.commit();
Thread.sleep(receiverWaitTime);
}
}
} catch (JMSException e) {
System.err.println("Linked Exception");
e.getLinkedException();
System.err.println("Error Code");
e.getErrorCode();
System.err.println("Cause ");
e.getCause();
System.err.println("fillTrackTrace ");
e.fillInStackTrace();
e.printStackTrace();
break;
}catch(IllegalStateException e) {
e.printStackTrace();
break;
}catch(InterruptedException e) {
e.printStackTrace();
break;
}catch(Exception e) {
e.printStackTrace();
break;
}
}
The receive(timeout) method will wait for the specified timeout period for a message to arrive on a queue. If a message arrives on a queue before the timeout, the method will return immediately with a message otherwise the method will wait till the timeout period and then return with no message. You will see a 2033 exception.
The timeout specified for the receive() call indicates how long the receive method must wait for messages before it can return. The timeout specified is not to delay the message delivery. If there is a message, the method will return immediately.
I think your logic can be modified to alter the order of execution. Change the code to receive messages only when your web service is ready to process messages.

Resources