Best practices to improve performance in JMS
This topic illustrates the performance improvement best practices in JMS with the following sections:
Overview of JMS
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
- Point to Point (PTP)
- 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.
Optimization with Connection
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 :
- Start the Connection when appropriate
- Process messages concurrently
- 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 .
3. Close the Connection when finished
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.
Optimization with Session
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.
1. Choose proper acknowledgement mode
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.
2. Control Transaction
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.
3. Close the Session when finished
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.
Optimization with Destination
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 :
- Maximum size of the Destination
- Maximum number of messages in the Destination
- Priority of messages
- Time to live
- Time to deliver
- Delivery mode
- Redelivery delay
- 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.
Optimization with Message Producer/Consumer
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
1. Choose non-durable messages where appropriate
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.
2. Set TimeToLive value properly
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.
3. Receive messages asynchronously
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.
4. Close Producer/Consumer when finished
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.
Optimization with Message
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 right JMS server
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.
http://www.sonicsoftware.com/white_papers/fiorano.pdf
Fiorano's JMS server FioranoMQ benchmarks, a benchmark comparison between
FioranoMQ and SonicMQ JMS servers.
http://www.fiorano.com/products/performance_comparison.htm
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.
- The type of message model you want to use in your model either PTP or
pub-and-sub or a combination of both.
- The volume of messages and the rate of message flow (messages per second).
- The number of applications involved.
- The message type and size
- The message delivery mode - durable/non-durable
- The number of connections to be opened.
- Vendor specific optimization features
- Support for clustering, which gives good scalability.
The above mentioned points can be looked into when choosing a JMS server for
your application.
Key Points
- Start producer connection after you start consumer.
- Use concurrent processing of messages.
- Close the Connection when finished.
- Choose either DUPS_OK_ACKNOWLEDGE or
AUTO_ACKNOWLEDGE rather than
CLIENT_ACKNOWLEDGE.
- Control transactions by using separate transactional session for
transactional messages and non-transactional session for non-transactional
messages.
- Close session object when finished.
- Make Destination with less capacity and send messages accordingly.
- Set high Redelivery delay time to reduce network traffic.
- Set less Redelivery limit for reducing number of message hits.
- Choose non-durable messages wherever appropriate to avoid persistence
overhead.
- Set optimal message age (TimeToLive value).
- Receive messages asynchronously.
- Close Producer/Consumer when finished.
- Choose message type carefully to avoid unnecessary memory overhead.
- Use 'transient' key word for variables of ObjectMessage which need not be
transferred.
|
|