Wednesday, January 22, 2014

Best practices to improve performance in JMS
This topic illustrates the performance improvement best practices in JMS with the following sections:
Java Message Service (JMS) is a Java API to access Message-Oriented-Middle ware products (MOM) products. These MOM products implement JMS API so that  java application can use JMS API in a vendor neutral manner. JMS clients communicate with each other using messages, this communication occurs in an asynchronous manner, that is when a sender sends a message then it does not wait for the response but continues its flow of execution.
There are two programming models in JMS API
  1. Point to Point (PTP)
  2. Publish and Subscribe (pub-and-sub)
In PTP model, a message is delivered to a single receiver only while in pub-and-sub a message is broadcasted to multiple receivers.
The following are the basic steps to write a JMS program
        1. Import javax.jms package   
        2. Lookup ConnectionFactory
        3. Create Connection
        4. Create Session
        5. Lookup Destination (Topic or Queue)
        6. Create Producers and Consumers
        7. Create Message
        8. Send and receive Messages
We will look at these areas one by one, how to optimize Connection, Session, Destination, Producer/Consumer, Message, and finally we will have a look at other optimization techniques to improve performance in JMS.
Note: This Section assumes that reader has some basic knowledge of JMS.
A connection represents an open connection (TCP/IP) from the JMS client to the JMS server. Connection is used to create Session objects that in turn create message producers and consumers.
A Connection object is created by ConnectionFactory  that could either be a TopicConnection or a QueueConnection.
When you create and use a Connection, consider the following optimization techniques : 
  1. Start the Connection when appropriate
  2. Process messages concurrently
  3. Close the Connection when finished
1. Start the Connection when appropriate
You need to start a  Connection using the start() method to start allowing the flow of messages from the producer to the JMS server. When do you start the Connection?
If you start a connection before starting the subscriber/Receiver (consumer) then the messages have to wait in the JMS server or they persist if they are durable messages, this is an unnecessary overhead so to avoid this ,first  start Consumers and then start the Producer Connection.
2. Process messages concurrently
JMS provides a facility to process messages concurrently by getting a ConnectionConsumer that uses server session pool. The server session pool is a pool of Sessions, each one executes separate message concurrently. This facility gives an application ability to process messages concurrently thus improving performance.
You can create ConnectionConsumer  using these methods.
For Queues :
public ConnectionConsumer createConnectionConsumer(Queue queue,
                                                                                    String messageSelector,
                                                                                    ServerSessionPool sessionPool,
                                                                                    int maxMessages) throws JMSException
For Topics :

public ConnectionConsumer createConnectionConsumer(Topic topic,
                                                                                    String messageSelector,
                                                                                    ServerSessionPool sessionPool,
                                                                                    int maxMessages) throws JMSException
In these methods the main parameters are maxMessages and ServerSessionPool. maxMessages denote the maximum number of messages that can be simultaneously assigned to a server session. ServerSessionPool is an administered object that you configure in vendor specific manner. This process works similar to Message driven beans where you can process multiple messages concurrently. This gives good performance and scalability. Some of the JMS vendors support this facility, but some vendors don't. So see your JMS server documentation for detailed information .
You need to close the external resources like network  or a database connection explicitly as soon as you are done with them. Similarly a JMS connection is also a TCP/IP  connection to the JMS server , you need to close the Connection using the close() method as and when you finish your work ,so that when you close the Connection it closes it's Session and Producer/Consumer objects also.

A Session is used to create multiple producers and consumers. A Session can be a QueueSession for a PTP or a TopicSession for a pub-and-sub model.
When you create a Session object, consider the following optimization techniques to improve performance.
1.   Choose proper acknowledgement mode
2.   Control Transaction
3.   Close the Session when finished.
When you create  a Session object, you can choose anyone of the three acknowledgement modes, AUTO_ACKNOWLEDGE, CLIENT_ACKNOWLEDGE or DUPS_OK_ACKNOWLEDGE. For example,
For Topic:
topicSession=topicConnect.createTopicSession(false, Session.CLIENT_ACKNOWLEDGE);
For Queue:
qsession=queueConnect.createQueueSession(false, session.AUTO_ACKNOWLEDGE);
Here you have a choice of choosing an acknowledgement among three modes. Each of these modes has a specific functionality. As per performance perspective, which mode gives the best performance?
CLIENT_ACKNOWLEDGE mode is not a feasible option (when you have the freedom to choose from the other two options) since the JMS server cannot send subsequent messages till it receives an acknowledgement from the client.
AUTO_ACKNOWLEDGE mode follows the policy of delivering the message once-and-only once but this incurs an overhead on the server to maintain this policy .
DUPS_OK_ACKNOWLEDGE mode has a different policy of sending the message more than once thereby reducing the overhead on the server (imposed when using the  AUTO_ACKNOWLEDGE) but imposes an overhead on the network traffic by sending the message more than once. But the  AUTO_ACKNOWLEDGE mode cleans up resources early from the persistent storage/memory which reduces the overhead because of that.
In summary, AUTO_ACKNOWLEDGE or DUPS_OK_ACKNOWLEDGE give better performance than CLIENT_ACKNOWLEDGE.
In JMS a transaction is a group of messages that are consumed/delivered  in all-or-none fashion. You make messages as transactional messages by giving 'true' flag when creating a session.
topicSession = tConnect.createTopicSession(true,Session.AUTO_ACKNOWLEDGE);
queueSession = qConnect.createQueueSession(true,Session.AUTO_ACKNOWLEDGE);
In the above code the first parameter indicates the session as transactional session. Session has commit(), rollback() and isTransacted() methods to deal with transactional messages. The problem here is that a transaction starts implicitly when session is created and ends when commit() or rollback() method is called. At this stage, after calling commit() or rollback() method, one more transaction starts implicitly because there is no explicit method (begin() method) to start a transaction . So there are a chain of transactions that depend upon commit() or rollback() method calls. Transactional messages are cumulated at JMS server until the transaction is committed or rolledback this imposes significant overhead on JMS server.
Suppose if you want to send 100 messages, out of which you want only 10 to messages in a transaction, How would you control transaction in such situations? The best method is to divide transactional messages and non-transactional messages separately. Create transactional session for transactional messages by giving 'true' flag (see code above) and create a separate non-transactional session for non-transactional messages by giving 'false' flag. This way, you can control transaction in order to improve performance.
It is always better to remove an object as early as possible when finished with ,although closing  a connection class closes session, this allows the garbage collector to remove objects earlier.

Destination (Topic/Queue) is a virtual channel between producers and consumers. Producers send messages to the Destination which in turn deliver messages to consumers. Destination encapsulates the vendor specific name.
Destination (Topic/Queue) is configured in a vendor specific manner, The following parameters can be configured :
  1. Maximum size of the Destination
  2. Maximum number of messages in the Destination
  3. Priority of messages
  4. Time to live
  5. Time to deliver
  6. Delivery mode
  7. Redelivery delay
  8. Redelivery limit
We need to look up JNDI to get Destination object.
InitialContext ic = new InitialContext(properties);
Topic topic = (Topic) ic.lookup(topicName);
Queue queue = (Queue) ic.lookup(queueName);
All the above configurable parameters have an impact on the performance. Here we will discuss the size of Destination, maximum messages in the Destination, Redelivery delay and Redelivery limit.
For non-durable messages, the time that messages take to deliver to the Destination depends upon its number and Destination size. If a large number of messages collect in a Destination, they take more time to deliver. So give less Destination size and less number of maximum messages to the Destination to improve performance.
Redelivery delay time defines when to redeliver a message if a failure occurs. If this is less, the frequency of redelivery of a message is high thus increasing network traffic and vice versa. So high Redelivery delay time gives better performance. Redelivery Limit defines the number of times a message should be redelivered. Although the probability of guaranteed messaging is less, if the Redelivery limit is less, then the performance is better because the memory overhead for non durable messages and persistent overhead for durable messages is reduced.So set optimal Redelivery limit to improve performance.

Producer(Sender/Publisher) sends messages to the Destination(Queue/Topic) where as Consumer(Receiver/Subscriber) consumes messages from the Destination. Message Producer/Consumer is created by Session object.
For Topics:
TopicPublisher publisher = pubSession.createPublisher(topic);
TopicSubscriber subscriber = subSession.createSubscriber(topic);
For Queues:
QueueSender sender = sendSession.createSender(queue);
QueueReceiver receiver = receiverSession.createReceiver(queue);
you send the messages using Producer,
For Topics:
publisher.publish(Message message); or
publisher.publish(Topic topic, Message message, int deliveryMode, int priority, long timeToLive);

For Queues:
sender.send(Message message); or
sender.send(Queue queue, Message message, int deliveryMode, int priority, long timeToLive);
The parameters DeliveryMode and TimeToLive are important from performance perspective. You can give values for these parameters when you configure ConnectionFactory or Destination or when you send a message (see above).
When you send the message using send() method or when you configure the delivery mode and timeToLive parameters in ConnectionFactory or Destination, consider the following optimization techniques to improve performance.
1. Choose non-durable messages where appropriate
2. Set TimeToLive value properly
3. Receive messages asynchronously
4. Close Producer/Consumer when finished

Delivery mode defines whether the message can be durable/persistent or non-durable/non-persistent, this factor has an impact on the performance. This parameter ensures that message delivery is guaranteed. For durable messages the delivery mode value is Deliverymode.PERSISTENT, for non-durable messages delivery mode value is Deliverymode.NON_PERSISTENT.
If you define the delivery mode as durable then the message is stored by the JMS server before delivering it to the consumer. The following figure illustrates this  process.

If you define the delivery mode as non-durable then the message is delivered to the consumer without being saved by the JMS server. The following figure illustrates this.

The above figures clearly show the difference between the two delivery modes. When using the durable delivery mode, each message has to be stored by the JMS server either in the database or the file system depending on the vendor before delivery of message to consumer and removed after delivery of message. This has a huge impact on the performance. So as far as possible restrict the use of durable delivery mode unless and until absolutely necessary for your application to avoid the overheads involved.
You can set the age of the message by setting the Time_To_Live parameter after which the message expires. By default the message never expires ,set  optimal message age so as to reduce memory overhead, thus improving performance.
You can receive messages synchronously or asynchronously. For recieving asynchronous messages you need to implement the MessageListener interface and onMessage() method. For receiving synchronous messages you need to use anyone of the following methods of MessageConsumer :
receive();
receive(long timeout);
receiveNoWait();
The first method blocks the call until it receives the next message, the second method blocks till a timeout occurs and the last method never blocks . When using asynchronous messaging the calls are never blocked so it is a better option to receive messages asynchronously by implementing MessageListener to improve performance.
It is always better to remove an object as early as possible when finished with, although closing  a connection class closes session and Producer/Consumer, this allows the garbage collector to remove objects earlier.

A Message object contains information that is passed between applications. It contains information as payload, headers and properties. As per performance perspective, you need to mainly consider the type of message - Text, Object ,Byte , Stream or Map message. The message size depends on the type of message you choose which in turn has an impact on the performance.
Less size gives better performance and vice versa. For example, ByteMessage takes less memory than TextMessage. ObjectMessage carries a serialized java object, when you choose ObjectMessage you need to use 'transient' keyword for variables that need not be sent over the network to reduce overhead. See Serialization for detailed information.
In summary choose message type carefully to avoid unnecessary memory overhead.

Choosing the right JMS server for best performance might be a difficult task since every vendor claims that  their server is the best server. Here are a few links which have the benchmarks of various servers.
Sonic software's JMS server SonicMQ benchmarks, a benchmark comparison between SonicMQ and FioranoMQ JMS servers.
Fiorano's JMS server FioranoMQ benchmarks, a benchmark comparison between FioranoMQ and SonicMQ JMS servers.

After seeing the benchmarks it may indeed be confusing for you to decide on a particular server , but here are a few points for your own bench mark testing and choosing a JMS server.
  1. The type of message model you want to use in your model either PTP or pub-and-sub or a combination of both.
  2. The volume of messages and the rate of message flow (messages per second).
  3. The number of applications involved.
  4. The message type and size
  5. The message delivery mode -  durable/non-durable
  6. The number of connections to be opened.
  7. Vendor specific optimization features
  8. Support for clustering, which gives good scalability.
The above mentioned points can be looked into when choosing a JMS server for your application.

  1. Start producer connection after you start consumer.
  2. Use concurrent processing of messages.
  3. Close the Connection when finished.
  4. Choose either DUPS_OK_ACKNOWLEDGE or AUTO_ACKNOWLEDGE rather than CLIENT_ACKNOWLEDGE.
  5. Control transactions by using separate transactional session for transactional messages and non-transactional session for non-transactional messages.
  6. Close session object when finished.
  7. Make Destination with less capacity and send messages accordingly.
  8. Set high Redelivery delay time to reduce network traffic.
  9. Set less Redelivery limit for reducing number of message hits.
  10. Choose non-durable messages wherever appropriate to avoid persistence overhead.
  11. Set optimal message age (TimeToLive value).
  12. Receive messages asynchronously.
  13. Close Producer/Consumer when finished.
  14. Choose message type carefully to avoid unnecessary memory overhead.
  15. Use 'transient' key word for variables of ObjectMessage which need not be transferred.

Feed back
We appreciate and welcome your comments on this section. Email commentsZZZ@precisejavaZZZ.com (remove ZZZ which is placed to prevent spam). Please note that we may not be able to reply to all the emails due to huge number of emails that we receive but we appreciate your comments and feedback.

No comments: