Thursday, October 22, 2009

JMS and Transaction

When we talk about JTA, we are thinking about all the actions in a business activity as a single unit that will either succeed or fail. When it comes to JMS, one may think of the following scenario. You do some business which involves some database action. You also send messages. And the messages are received by the clients. You may think that this whole thing will be a transaction unit. The special thing here is that it involves both the sending and receiving of the messages in this unit. You may even think that this has to do with the so-called synchronous and asynchronous messaging. But all these may be some wrong impression about JMS.

JMS and JTA

Firstly, the JMS JTA is not about to put both the sending and receiving of messages in an atomic unit. In reality, you may just want to make sure that only one of the sending and receiving actions will be part of the transaction.

The following are the behaviors regarding to the message production and consumption.

For message production, when an application commits a transaction, all the messages it sent within the transaction are actually delivered. If the application rolls back the transaction, the messages it sent are discarded.

For message consumption, when an application commits a transaction, all the messages it received within the transaction are removed from the messaging system.
When a topic subscriber rolls back a received message, the message is redelivered to that subscriber.
When a queue receiver rolls back a received message, the message is redelivered to the queue, not the consumer, so that another consumer on that queue may receive the message.

Note: In weblogic console, you can set a Redelivery limit for both the topic and the queue. It is the number of redelivery tries a message can have before it is moved to the error destination. This setting overrides any redelivery limit set by the message sender. If the redelivery limit is configured, but no error destination is configured, then persistent and non-persistent messages are simply dropped (deleted) when they reach their redelivery limit.

Secondly, even though sending and receiving messages can be in two separate JTA transactions, the effect will still be like that they are in one unit. This will be mostly the responsibility of the message provider, or more technically the Message-Oriented Middleware (MOM), that implements the JMS service. After the message is produced, the message provider will do the redelivery if the message receiving is rolled back.

So what if you do want to have both the sending and receiving of the messages in the same atomic unit?
The answer: It is not possible! References [4] and [5] have very good explanations on this. Your application cannot both send a JMS message and receive a reply to it within the same transaction. This is true no matter if you use local transaction in JMS or global transaction in a J2EE environment. Because a message sent during a transaction is not actually sent until the transaction is committed, the transaction cannot contain any receives that depend on that message's having been sent. You can, however, receive a message and send a message in one transaction. Note here that the order of send/receive matters. Also I think that if you send a message and receive an unrelated message, then it is also OK to put them in one transaction. The important thing about this scenario is that the received message must be unrelated to the message sent so it does not depend on that message being sent successfully. This scenario may not make sense in real life. After all, why do you need to put two unrelated actions into one transaction?

By the way, in a non-transactional session, the action that is similar to a commit in transactional session may be the message acknowledgement. When the Session.CLIENT_ACKNOWLEDGE mode is used in a non-transactional session, the following holds according to Reference[4]:

  1. In this mode, acknowledgement takes place on the session level. So acknowledging a consumed message automatically acknowledge the receipt of all the messages that have been consumed by the session. So for example, if a consumer consumes 10 messages and then acknowledge the 5th message, all 10 messages are acknowledged.
  2. In the above statement, we can see that even though the 5th message is not the last message among the 10 messages, the effect is still that all the 10 messages are now acknowledged. But also notice that these 10 messages have been received at the time the 5th message sends its acknowledgement. So for the messages the consumer will receive in the future, new acknowledgement will need to be sent.

Synchronous and Asynchronous Messaging

Synchronous and asynchronous messaging is not about that a message will be sent and received in one atomic action unit. It is about if the call to receive the message will be blocked.

A JMS client can consume messages either synchronously or asynchronously.
  1. Synchronous: In this mode, a client receives a message by invoking the receive() method of the MessageConsumer object. The application thread blocks until the method returns, and this has the consequence that if a message is not available, it blocks until a message becomes available or the receive() method times out. Also, note that in this model the client can consume one message at a time.
    A long parameter can be passed to the receive() method to specify a time-out (for example, 3000L for 3 seconds).
  2. Asynchronous: In this mode, the client registers a MessageListener object with a message consumer. This is like a call-back where the client consumes a message when the session invokes the onMessage() method. In other words, the application's thread doesn't block.

Persistent and Non-persistent messages

JMS defines two delivery modes:
  1. Persistent messages: Guaranteed to be successfully consumed once and only once. Messages are not lost.
  2. Non-persistent messages: Guaranteed to be delivered at most once. Message loss is not a concern.
This, however, is all about performance trade-offs. The more reliable the delivery of messages, the more bandwidth and overhead required to achieve that reliability. Performance can be maximized by producing non-persistent messages, or you can maximize the reliability by producing persistent messages.

The following is from javadoc of javax.jms.DeliveryMode.

public interface DeliveryMode The delivery modes supported by the JMS API are PERSISTENT and NON_PERSISTENT.

A client marks a message as persistent if it feels that the application will have problems if the message is lost in transit. A client marks a message as non-persistent if an occasional lost message is tolerable. Clients use delivery mode to tell a JMS provider how to balance message transport reliability with throughput.

Delivery mode covers only the transport of the message to its destination. Retention of a message at the destination until its receipt is acknowledged is not guaranteed by a PERSISTENT delivery mode. Clients should assume that message retention policies are set administratively. Message retention policy governs the reliability of message delivery from destination to message consumer. For example, if a client's message storage space is exhausted, some messages may be dropped in accordance with a site-specific message retention policy.

A message is guaranteed to be delivered once and only once by a JMS provider if the delivery mode of the message is PERSISTENT and if the destination has a sufficient message retention policy.

The delivery mode is configured on the connection factory. In weblogic console, you can set a value for the "Default Delivery Mode" property of a connection factory. The value is either Persistent or Non-Persistent. This is the default delivery mode used for messages when a delivery mode is not explicitly defined. All messages with a DefaultDeliveryMode of null that are produced on a connection created with this factory will receive this value. Message producers can get the delivery mode explicitly by calling the javax.jms.MessageProducer.getDeliveryMode() method. This attribute is dynamic. It can be changed at any time. However, changing the value does not affect existing connections. It only affects new connections made with this connection factory.

References

1. http://java.sun.com/developer/technicalArticles/Ecommerce/jms/index.html
2. http://download.oracle.com/docs/cd/E12840_01/wls/docs103/jms/trans.html
3. http://openmessaging.blogspot.com/2009/04/durable-messages-and-persistent.html
4. http://docs.oracle.com/javaee/1.4/tutorial/doc/JMS6.html
5. http://docs.oracle.com/javaee/1.4/tutorial/doc/JMS7.html

No comments:

Post a Comment