I'm using ActiveMQ on a simulation of overloading servers in Java. And mainly it goes ok, but when I get over 600 requests the thing just go WTF!
I think the bottleneck is my Master Server which is this guy below. I'm already reusing the connection and creating various sessions to consume messages from clients. Like I said, I'm using about 50-70 sessions per connection, reutilizing the connection and queue. Any idea of what I can reuse/optimize of my components/listener below?
The architecture is the follow:
* = various
Client ---> JMS MasterQueue ---> * Master ---> JMS SlavaQueue ---> * SlaveQueue
Mainly I'm creating a Temp Queue for each session of Master --> Slave communication, is that a big problem on performance?
/**
* This subclass implements the processing log of the Master JMS Server to
* propagate the message to the Server (Slave) JMS queue.
*
* #author Marcos Paulino Roriz Junior
*
*/
public class ReceiveRequests implements MessageListener {
public void onMessage(Message msg) {
try {
ObjectMessage objMsg = (ObjectMessage) msg;
// Saves the destination where the master should answer
Destination originReplyDestination = objMsg.getJMSReplyTo();
// Creates session and a sender to the slaves
BankQueue slaveQueue = getSlaveQueue();
QueueSession session = slaveQueue.getQueueConnection()
.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
QueueSender sender = session
.createSender(slaveQueue.getQueue());
// Creates a tempQueue for the slave tunnel the message to this
// master and also create a masterConsumer for this tempQueue.
TemporaryQueue tempDest = session.createTemporaryQueue();
MessageConsumer masterConsumer = session
.createConsumer(tempDest);
// Setting JMS Reply Destination to our tempQueue
msg.setJMSReplyTo(tempDest);
// Sending and waiting for answer
sender.send(msg);
Message msgReturned = masterConsumer.receive(getTimeout());
// Let's check if the timeout expired
while (msgReturned == null) {
sender.send(msg);
msgReturned = masterConsumer.receive(getTimeout());
}
// Sends answer to the client
MessageProducer producerToClient = session
.createProducer(originReplyDestination);
producerToClient.send(originReplyDestination, msgReturned);
} catch (JMSException e) {
logger.error("NO REPLY DESTINATION PROVIDED", e);
}
}
}
Well, After some reading I found out how to optimize.
We should reuse some session variables, such as the sender and tempqueue. Instead of creating new ones.
Another approach is put the stack size for thread in Java lower, following this link
ActiveMQ OutOfMemory Can't create more threads
It could have to do with the configuration of the listener thread pool. It could be that up to a certain threshold number of requests per second the listener is able to keep up and process the incoming requests in a timely way, but above that rate it starts to fall behind. it depends on the work done for each incoming request, the incoming request rate, the memory and CPU available to each listener, and the number of listeners allocated.
If this is true, you should be able to watch the queue and see when the number of incoming messages start to back up. That's the point at which you need to increase the resources and number of listeners to process efficiently.
Related
After I went to multiple sites and learned JMS I wrote a JMS standalone client to read messages from a database and send them one by one. I also want to receive message one by one message and then update the database.
I need to send a message to a queue and the other application using standard JMS which will consume a TextMessage and whose body will be read as an ISO-8859-1 string. Also they will similarly send reply as a TextMessage.
I wrote a for loop to read the message one by one from the DB.
I am new to JMS so could you please correct me whether my below code works properly to read and send messages to a queue and receive and update the DB. Is there any thing I need to change in the JMS Type or any thing I need to correct. Does the for loop work fine?
/*MQ Configuration*/
MQQueueConnectionFactory mqQueueConnectionFactory = new MQQueueConnectionFactory();
mqQueueConnectionFactory.setHostName(url);
mqQueueConnectionFactory.setChannel(channel);//communications link
mqQueueConnectionFactory.setPort(port);
mqQueueConnectionFactory.setQueueManager(qmgr);//service provider
mqQueueConnectionFactory.setTransportType(JMSC.MQJMS_TP_CLIENT_MQ_TCPIP);
/*Create Connection */
QueueConnection queueConnection = mqQueueConnectionFactory.createQueueConnection();
queueConnection.start();
/*Create session */
QueueSession queueSession = queueConnection.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
/*Create response queue */
// Queue queue = queueSession.createQueue("QUEUE.RESPONSE");
int messageCount = 0;
Queue queue = queueSession.createQueue(replytoQueueName);
QueueSender queueSender = null;
QueueReceiver queueReceiver=null;
for (Testbean testBean : testbeanList) {
String testMessage = testBean.getMessage();
/*Create text message */
textMessage = queueSession.createTextMessage(testMessage);
logger.info("Text messages sent: " + messageCount);
textMessage.setJMSReplyTo(queue);
textMessage.setJMSType("mcd://xmlns");//message type
textMessage.setJMSExpiration(2*1000);//message expiration
textMessage.setJMSDeliveryMode(DeliveryMode.PERSISTENT); //message delivery mode either persistent or non-persistemnt
/*Create sender queue */
// QueueSender queueSender = queueSession.createSender(queueSession.createQueue("QUEUE.REQEST"));
queueSender = queueSession.createSender(queueSession.createQueue(outputQName));
queueSender.setTimeToLive(2*1000);
queueSender.send(textMessage);
/*After sending a message we get message id */
System.out.println("after sending a message we get message id "+ textMessage.getJMSMessageID());
String jmsCorrelationID = " JMSCorrelationID = '" + textMessage.getJMSMessageID() + "'";
/*Within the session we have to create queue reciver */
queueReceiver = queueSession.createReceiver(queue,jmsCorrelationID);
/*Receive the message from*/
Message message = queueReceiver.receive(60*1000);
// String responseMsg = ((TextMessage) message).getText();
byte[] by = ((TextMessage) message).getText().getBytes("ISO-8859-1");
logger.info(new String(by));
String responseMsg = new String(by,"UTF-8");
testDAO rmdao = new testDAO();
rmdao.updateTest(responseMsg, jmsCorrelationID);
messageCount += 1;
}
queueSender.close();
queueReceiver.close();
queueSession.close();
queueConnection.close();
Couple of things:
I would create your QueueSender and the Queue object its sending messages to outside the for loop since they don't appear to be changing.
Without the corresponding consumer code it's ultimately impossible to tell if the selector will work or not, but not invoking setCorrelationID() on the message you send looks a bit strange to me. Using the provider-assigned message ID may be a common pattern with IBM MQ request/reply applications, but the general pattern for using a correlation ID is to invoke setJMSCorrelationID() on the sent message. This makes the code more clear and also allows the application to directly control the uniqueness of the correlation ID. This is potentially important for application portability (e.g. if you migrated from IBM MQ to a different JMS provider) since different JMS providers use styles/formats of message ID specific to their particular implementation. Also, regarding the message ID the JMS spec states, "The exact scope of uniqueness is provider defined," which in my opinion is not a strong enough guarantee of uniqueness especially when using something like java.util.UUID.randomUUID().toString() is so simple.
You should ensure that you're using an XA transaction for both the JMS & database work so that they are atomic.
Close your JMS resources in a finally block.
I have a problem when reading messages from multiple JMS Queues in a single transaction using WebLogic JMS client (wlthin3client.jar) from WebLogic 11g (WebLogic Server 10.3.6.0). I am trying to read first one message from queue Q1 and then, if this message satisfy some requirements, read other message (if available at that time) from queue Q2.
I expect that after committing the transaction both messages should disappear from Q1 and Q2. In case of rollback - messages should remain in both Q1 and Q2.
My first approach was to use an asynchronous queue receiver to read from Q1 and then synchronously read from Q2 when it is needed:
void run() throws JMSException, NamingException {
QueueConnectionFactory cf = (QueueConnectionFactory) ctx.lookup(connectionFactory);
// create connection and session
conn = cf.createQueueConnection();
session = conn.createQueueSession(true, Session.SESSION_TRANSACTED);
Queue q1 = (Queue) ctx.lookup(queue1);
// setup async receiver for Q1
QueueReceiver q1Receiver = session.createReceiver(q1 );
q1Receiver.setMessageListener(this);
conn.start();
// ...
// after messages are processed
conn.close();
}
#Override
public void onMessage(Message q1msg) {
try {
QueueReceiver q2receiver = session.createReceiver(queue2);
if(shouldReadFromQ2(q1msg)){
// synchronous receive from Q2
Message q2msg = q2receiver.receiveNoWait();
process(q2msg);
}
session.commit();
} catch (JMSException e) {
e.printStackTrace();
} finally {
q2receiver.close();
}
}
Unfortunately even though I issue a session.commit() the message from Q1 remains uncommitted. It is in receive state until the connection or receiver is closed. Then is seems to be rolled back as it gets delayed state.
Other observations:
Q1 message is correctly committed if Q2 is empty and there is nothing to read from it.
The problem does not occur when I am using synchronous API in similar, nested way for both Q1 and Q2. So if I use q1Receiver.receiveNoWait() everything is fine.
If I use asynchronous API in similar, nested way for Q1 and Q2, then only Q1 message listener is called and commit works on Q1. But Q2 message listener is not called at all and Q2 is not committed (message stuck in receive / delayed).
Am I misusing the API somehow? Or is this a WLS JMS bug? How to combine reading from multiple queues with asynchronous API?
It turns out this is an WLS JMS bug 28637420.
The bug status says it is fixed, but I wouldn't rely on this - a WLS 11g patch with this fix doesn't work (see bug 29177370).
Oracle says that this happens because two different delivery mechanisms (synchronous messages vs asynchronous messages) were not designed to work together on the same session.
Simplest way to work around the problem is just use synchronous API (polling) for cases when you need to work on multiple queues in a single session. I decided on this approach.
Another option suggested by oracle is to to use UserTransactions with two different sessions, one session for the async consumer and another session for the synchronous consumer. I didn't test that though.
I am using IBM MQ to produce messages while receiving it through a consumer on my client. To create the connection I'm using JmsConnectionFactory, along with provided properties to set up the connection with the server. So from what I understand is, as the consumer the only way to recognize the messages produced by the server is through the onMessage call. I'm currently testing this by creating a local producer and local consumer and assuring that every message sent by the producer is received by the consumer.
I'm running into the following problems:
I'm not receiving all messages produced.
Depending on the size of the message, more of them are received if they are smaller.
Here is code for the creation of the producer:
JmsConnectionFactory cf = ff.createConnectionFactory();
cf.setStringProperty(WMQConstants.WMQ_HOST_NAME, qm.getHost());
int port = ###;
cf.setIntProperty(WMQConstants.WMQ_PORT, port);
cf.setStringProperty(WMQConstants.WMQ_CHANNEL, qm.getChannel());
cf.setIntProperty(WMQConstants.WMQ_CONNECTION_MODE, WMQConstants.WMQ_CM_CLIENT);
cf.setStringProperty(WMQConstants.WMQ_QUEUE_MANAGER, qm.getQueueManagerName());
Connection connection = cf.createConnection(qm.getUser().getUsername(), qm.getUser().getPassword());
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Destination destination = session.createQueue(qm.getDestinationName());
LOG.debug("Destination Created at " +qm.getDestinationName());
msgSender = session.createProducer(destination);
msgSender.setDeliveryMode(DeliveryMode.PERSISTENT);
And this is how the producer is sending messages:
/**
* msgSender is the MessageProducer object
**/
private void produceMessages(int numOfMessages) throws JMSException, InterruptedException {
for (int i = 0; i < numOfMessages; i++) {
String text = "Message #" +i;
TextMessage message = session.createTextMessage(text);
msgSender.send(message);
}
}
On the consumer side, I am simply printing received messages and verifying visually:
#Override
public void onMessage(Message m) {
System.out.println(((TextMessage)m).getText());
}
I am not fully familiar with how IBM MQ works. Could the reason for the missing messages reside on the MQ simply ignoring messages that are produced before a message is fully sent?
I would say the issue is residing on your consumer side, rather than your simulated producer. Your message producer should be sending messages to MQ just fine, but multiple consumers are probably competing to retrieve these messages from the connection you have set up (given the same queue manager properties). So unless no one else is trying to consume from your IBM MQ, you're going to be expected to miss some messages.
You should use other method of send(Message m, CompletionListener l) to send new messages only after completion.
And if you use "Best Effort", it still will lose messages. You can try "Express" instead.
I am sending some messages to a JMS queue. What are the possible ways to search for a particular message in a queue to consume?
I tried out in the following way: I am setting the JMSCorrelationID while sending a message to the queue:
public void createDQueue(String queuename, String json, Integer userid) {
try {
QueueSession.AUTO_ACKNOWLEDGE );
Queue queue = session.createQueue(queuename);
ObjectMessage objectMessage = session.createObjectMessage();
objectMessage.setJMSCorrelationID(String.valueOf(userid));
objectMessage.setObject(json);
session.createSender(queue).send(objectMessage);
session.close();
connection.close();
}catch(Exception e){
e.printStackTrace();
}
}
In the consumer code I want to get that particular message based on the JMSCorrelationID. I am not able to get that particular message. Can you suggest a solution?
public void getSpecificMessage(String queuename, Integer userid) {
try {
QueueConnectionFactory connectionFactory = new ActiveMQConnectionFactory( "tcp://localhost:61616");
((ActiveMQConnectionFactory) connectionFactory).setUseAsyncSend(true);
QueueConnection connection = connectionFactory.createQueueConnection();
connection.start();
QueueSession session = connection.createQueueSession( false,
QueueSession.AUTO_ACKNOWLEDGE );
String id = String.valueOf(userid);
Queue queue = session.createQueue(queuename);
QueueReceiver receiver = session.createReceiver(queue, "JMSCorrelationID="+id);
Message message = receiver.receive();
} catch (JMSException e) {
e.printStackTrace();
}
}
Your first problem is that you are trying to think about the message broker as a database, you must always remember this sage piece of advice, "A message broker is not a database".
There are certain limits on how deep a consumer or Queue browser can go into a destination before the broker will not page in more messages from disk, so you need to check your depth and see if its large than you maxPageSize setting and adjust as needed, but remember that messages paged in remain in memory until consumed.
Just wrap the id value in single quotes
"JMSCorrelationID='"+id+"'"
This functionality is not recommended to be used , there are lot more complications as explained by Tim , but if you want to obsolutely work with it make the change
You can search messages using the MeessageID of a message. This would be fast as messaging providers index messages on message id. There are other way to search based on CorrelationId, meta data etc.
But please remember the primary objective of using a messaging provider is to connect applications in a time independent manner. The receiving application must get messages as soon as possible. If messages are piling up in a queue, it indicates a problem that must be addressed.
I'm having a bit of trouble with understanding JMS from a performance perspective. We have this very straightforward code in our application:
QueueConnection connection = null;
QueueSession session = null;
QueueSender sender = null;
TextMessage msg = null;
try {
// The JNDIHelper uses InitialContext to look up things
QueueConnectionFactory qcf = JNDIHelper.lookupFactory();
Queue destQueue = JNDIHelper.lookupQueue();
// These objects are created for every message, which is quite slow
connection = qcf.createQueueConnection();
session = connection.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
sender = session.createSender(destQueue);
// This is the actual message
msg = session.createTextMessage(xmlMsg);
sender.setTimeToLive(0);
sender.send(msg);
}
finally {
// Close all objects again
JMSUtilities.safeClose(sender);
JMSUtilities.safeClose(session);
JMSUtilities.safeClose(connection);
}
The code is correct, but probably some of the above artefacts could be reused for several messages. These are our configurations:
We use Oracle Weblogic 10.3.3
Weblogic connects to IBM MQ 7.0 (Problem also appears with 6.0) for JMS
The above logic is executed by a single thread on a backend server. It would be simple to keep some objects (QueueConnection, QueueSession, QueueSender) in memory as there is no concurrency involved.
My questions
Which types of objects can be shared among several messages? (of course we'd include error recovery, restoring those shared objects)
What are best practices to improve performance?
Here are some relevant parts of the jms spec:
section 2.8 Multithreading
JMS Object Supports Concurrent Use
Destination YES
ConnectionFactory YES
Connection YES
Session NO
MessageProducer NO
MessageConsumer NO
section 4.4.14 Serial Execution of Client Code
JMS does not cause concurrent execution of client code unless a client explicitly requests it. One way this is done is to define that a session serializes all asynchronous delivery of messages
So as already mentioned reuse as much as possible. Reuse the ConnectionFactory, Connection and Destinations for all Threads. For each Thread reuse consumers and producers.
If you are reusing a JMS connection beware, that the JMS Provider will multiplex different sessions on that connections. So even if it is safe to reuse connections it might be faster to create a connection for every session you need.
The only thing you need to create again and again is the msg itself - if you are sending to the same queue.
So yes, you can remember the Connection, Session and Sender.
Define "to share".
If you mean to share among different threads this is very dangerous. You can safely share QueueConnectionFactory object as well as the JMS Connection object. You must not share Session, Sender/Consumer or Message objects. Thats the way how TIBCO EMS works I am not sure about IBM platform but I guess this is very same.
If you can be sure your "send" method is not called by different threads you can encapulate this into a MySender class with Connection, Session and Sender member variables. But watch out! Do properly close the resources on exit. Thats what Heiko Rupp recommends. Somthing like this:
class MySender {
private QueueConnection connection = null;
private QueueSession session = null;
private QueueSender sender = null;
public MySender(...) { /* initialize conn/sess/sender */ }
public send(String xmlMsg) { /* sender.send(session.createTextMessage(xmlMsg)) */ }
public close() { /* close all resources */ }
}
Regarding performance. There is no much room for improvement in JMS standard. Keep messages small and optimize server setting. Use durable destinations only when you need it etc. Read documentation for your platform. But on the client side there is not much room. Some platforms offers additional features to JMS that allows some extra performance gain (batch sends etc) but it depends on the platform. I dont know IBM.