|JavaTM Message Service Tutorial|
|Tutorial Homepage | TOC | Prev | Next | Index|
5 Creating Robust JMS Applications
This chapter explains how to use features of the JMS API to achieve the level of reliability and performance your application requires. Many JMS applications cannot tolerate dropped or duplicate messages and require that every message be received once and only once.
The most reliable way to produce a message is to send a
PERSISTENTmessage within a transaction. JMS messages are
PERSISTENTby default. A transaction is a unit of work into which you can group a series of operations, such as message sends and receives, so that the operations either all succeed or all fail. For details, see Section 5.1.2, "Specifying Message Persistence," and Section 5.2.2, "Using JMS API Local Transactions."
The most reliable way to consume a message is to do so within a transaction, either from a nontemporary queue--in the PTP messaging domain--or from a durable subscription--in the pub/sub messaging domain. For details, see Section 5.1.5, "Creating Temporary Destinations"; Section 5.2.1, "Creating Durable Subscriptions"; and Section 5.2.2, "Using JMS API Local Transactions."
For other applications, a lower level of reliability can reduce overhead and improve performance. You can send messages with varying priority levels--see Section 5.1.3, "Setting Message Priority Levels"--and you can set them to expire after a certain length of time (see Section 5.1.4, "Allowing Messages to Expire").
The JMS API provides several ways to achieve various kinds and degrees of reliability. This chapter divides them into two categories:
The following sections describe these features as they apply to JMS clients. Some of the features work differently in J2EETM applications; in these cases, the differences are noted here and are explained in detail in Chapter 6.
5.1 Using Basic Reliability Mechanisms
The basic mechanisms for achieving or affecting reliable message delivery are as follows:
- Controlling message acknowledgment. You can specify various levels of control over message acknowledgment.
- Specifying message persistence. You can specify that messages are persistent, meaning that they must not be lost in the event of a provider failure.
- Setting message priority levels. You can set various priority levels for messages, which can affect the order in which the messages are delivered.
- Allowing messages to expire. You can specify an expiration time for messages, so that they will not be delivered if they are obsolete.
- Creating temporary destinations. You can create temporary destinations that last only for the duration of the connection in which they are created.
5.1.1 Controlling Message Acknowledgment
Until a JMS message has been acknowledged, it is not considered to be successfully consumed. The successful consumption of a message ordinarily takes place in three stages.
- The client receives the message.
- The client processes the message.
- The message is acknowledged. Acknowledgment is initiated either by the JMS provider or by the client, depending on the session acknowledgment mode.
In transacted sessions (see Section 5.2.2, "Using JMS API Local Transactions"), acknowledgment happens automatically when a transaction is committed. If a transaction is rolled back, all consumed messages are redelivered.
In nontransacted sessions, when and how a message is acknowledged depends on the value specified as the second argument of the
createTopicSessionmethod. The three possible argument values are:
Session.AUTO_ACKNOWLEDGE. The session automatically acknowledges a client's receipt of a message either when the client has successfully returned from a call to
receiveor when the
MessageListenerit has called to process the message returns successfully. A synchronous receive in an
AUTO_ACKNOWLEDGEsession is the one exception to the rule that message consumption is a three-stage process. In this case, the receipt and acknowledgment take place in one step, followed by the processing of the message.
Session.CLIENT_ACKNOWLEDGE. A client acknowledges a message by calling the message's
acknowledgemethod. In this mode, acknowledgment takes place on the session level: Acknowledging a consumed message automatically acknowledges the receipt of all messages that have been consumed by its session. For example, if a message consumer consumes ten messages and then acknowledges the fifth message delivered, all ten messages are acknowledged.
Session.DUPS_OK_ACKNOWLEDGE. This option instructs the session to lazily acknowledge the delivery of messages. This is likely to result in the delivery of some duplicate messages if the JMS provider fails, so it should be used only by consumers that can tolerate duplicate messages. (If it redelivers a message, the JMS provider must set the value of the
JMSRedeliveredmessage header to
true.) This option can reduce session overhead by minimizing the work the session does to prevent duplicates.
If messages have been received but not acknowledged when a
QueueSessionterminates, the JMS provider retains them and redelivers them when a consumer next accesses the queue. The provider also retains unacknowledged messages for a terminated
TopicSessionwith a durable
TopicSubscriber. (See Section 5.2.1, "Creating Durable Subscriptions.") Unacknowledged messages for a nondurable
TopicSubscriberare dropped when the session is closed.
If you use a queue or a durable subscription, you can use the
Session.recovermethod to stop a nontransacted session and restart it with its first unacknowledged message. In effect, the session's series of delivered messages is reset to the point after its last acknowledged message. The messages it now delivers may be different from those that were originally delivered, if messages have expired or higher-priority messages have arrived. For a nondurable
TopicSubscriber, the provider may drop unacknowledged messages when its session is recovered.
The sample program in Section A.3, "Acknowledgment Modes," demonstrates two ways to ensure that a message will not be acknowledged until processing of the message is complete.
5.1.2 Specifying Message Persistence
The JMS API supports two delivery modes for messages to specify whether messages are lost if the JMS provider fails. These delivery modes are fields of the
PERSISTENTdelivery mode, which is the default, instructs the JMS provider to take extra care to ensure that a message is not lost in transit in case of a JMS provider failure. A message sent with this delivery mode is logged to stable storage when it is sent.
NON_PERSISTENTdelivery mode does not require the JMS provider to store the message or otherwise guarantee that it is not lost if the provider fails.
You can specify the delivery mode in either of two ways.
- You can use the
setDeliveryModemethod of the
MessageProducerinterface--the parent of the
TopicPublisherinterfaces--to set the delivery mode for all messages sent by that producer.
- You can use the long form of the
publishmethod to set the delivery mode for a specific message. The second argument sets the delivery mode. For example, the following
publishcall sets the delivery mode for
NON_PERSISTENT:topicPublisher.publish(message, DeliveryMode.NON_PERSISTENT, 3, 10000);
If you do not specify a delivery mode, the default is
PERSISTENT. Using the
NON_PERSISTENTdelivery mode may improve performance and reduce storage overhead, but you should use it only if your application can afford to miss messages.
5.1.3 Setting Message Priority Levels
You can use message priority levels to instruct the JMS provider to deliver urgent messages first. You can set the priority level in either of two ways.
- You can use the
setPrioritymethod of the
MessageProducerinterface to set the priority level for all messages sent by that producer.
- You can use the long form of the
publishmethod to set the priority level for a specific message. The third argument sets the priority level. For example, the following
publishcall sets the priority level for
messageto 3:topicPublisher.publish(message, DeliveryMode.NON_PERSISTENT, 3, 10000);
The ten levels of priority range from 0 (lowest) to 9 (highest). If you do not specify a priority level, the default level is 4. A JMS provider tries to deliver higher-priority messages before lower-priority ones but does not have to deliver messages in exact order of priority.
5.1.4 Allowing Messages to Expire
By default, a message never expires. If a message will become obsolete after a certain period, however, you may want to set an expiration time. You can do this in either of two ways.
- You can use the
setTimeToLivemethod of the
MessageProducerinterface to set a default expiration time for all messages sent by that producer.
- You can use the long form of the
publishmethod to set an expiration time for a specific message. The fourth argument sets the expiration time in milliseconds. For example, the following
publishcall sets a time to live of 10 seconds:topicPublisher.publish(message, DeliveryMode.NON_PERSISTENT, 3, 10000);
If the specified timeToLive value is
0, the message never expires.
When the message is published, the specified timeToLive is added to the current time to give the expiration time. Any message not delivered before the specified expiration time is destroyed. The destruction of obsolete messages conserves storage and computing resources.
5.1.5 Creating Temporary Destinations
Normally, you create JMS destinations--queues and topics--administratively rather than programmatically. Your JMS or J2EE provider includes a tool that you use to create and to remove destinations, and it is common for destinations to be long lasting.
The JMS API also enables you to create destinations--
TemporaryTopicobjects--that last only for the duration of the connection in which they are created. You create these destinations dynamically, using the
The only message consumers that can consume from a temporary destination are those created by the same connection that created the destination. Any message producer can send to the temporary destination. If you close the connection that a temporary destination belongs to, the destination is closed and its contents lost.
You can use temporary destinations to implement a simple request/reply mechanism. If you create a temporary destination and specify it as the value of the
JMSReplyTomessage header field when you send a message, the consumer of the message can use the value of the
JMSReplyTofield as the destination to which it sends a reply and can also reference the original request by setting the
JMSCorrelationIDheader field of the reply message to the value of the
JMSMessageIDheader field of the request. For examples, see Chapter 9 and Chapter 10.
5.2 Using Advanced Reliability Mechanisms
The more advanced mechanisms for achieving reliable message delivery are the following:
- Creating durable subscriptions. You can create durable topic subscriptions, which receive messages published while the subscriber is not active. Durable subscriptions offer the reliability of queues to the publish/subscribe message domain.
- Using local transactions. You can use local transactions, which allow you to group a series of sends and receives into an atomic unit of work. Transactions are rolled back if they fail at any time.
5.2.1 Creating Durable Subscriptions
To make sure that a pub/sub application receives all published messages, use
PERSISTENTdelivery mode for the publishers. In addition, use durable subscriptions for the subscribers.
TopicSession.createSubscribermethod creates a nondurable subscriber. A nondurable subscriber can receive only messages that are published while it is active.
At the cost of higher overhead, you can use the
TopicSession.createDurableSubscribermethod to create a durable subscriber. A durable subscription can have only one active subscriber at a time.
A durable subscriber registers a durable subscription with a unique identity that is retained by the JMS provider. Subsequent subscriber objects with the same identity resume the subscription in the state in which it was left by the previous subscriber. If a durable subscription has no active subscriber, the JMS provider retains the subscription's messages until they are received by the subscription or until they expire.
You establish the unique identity of a durable subscriber by setting the following:
You set the client ID administratively for a client-specific connection factory using the
j2eeadmincommand. For example:j2eeadmin -addJmsFactory MY_CON_FAC topic -props clientID=MyID
After using this connection factory to create the connection and the session, you call the
createDurableSubscribermethod with two arguments--the topic and a string that specifies the name of the subscription:String subName = "MySub"; TopicSubscriber topicSubscriber = topicSession.createDurableSubscriber(myTopic, subName);
The subscriber becomes active after you start the
TopicConnection. Later on, you might close the
The JMS provider stores the messages published to the topic, as it would store messages sent to a queue. If the program or another application calls
createDurableSubscriberwith the same connection factory and its client ID, the same topic, and the same subscription name, the subscription is reactivated, and the JMS provider delivers the messages that were published while the subscriber was inactive.
To delete a durable subscription, first close the subscriber, and then use the
unsubscribemethod, with the subscription name as the argument:topicSubscriber.close(); topicSession.unsubscribe("MySub");
unsubscribemethod deletes the state that the provider maintains for the subscriber.
Figure 5.1 and Figure 5.2 show the difference between a nondurable and a durable subscriber. With an ordinary, nondurable, subscriber, the subscriber and the subscription are coterminous and, in effect, identical. When a subscriber is closed, the subscription ends as well. Here,
createstands for a call to
closestands for a call to
TopicSubscriber.close. Any messages published to the topic between the time of the first
closeand the time of the second
createare not consumed by the subscriber. In Figure 5.1, the subscriber consumes messages M1, M2, M5, and M6, but messages M3 and M4 are lost.
Figure 5.1 Nondurable Subscribers and Subscriptions
With a durable subscriber, the subscriber can be closed and recreated, but the subscription continues to exist and to hold messages until the application calls the
unsubscribemethod. In Figure 5.2,
createstands for a call to
closestands for a call to
unsubscribestands for a call to
TopicSession.unsubscribe. Messages published while the subscriber is closed are received when the subscriber is created again. So even though messages M2, M4, and M5 arrive while the subscriber is closed, they are not lost.
Figure 5.2 A Durable Subscriber and Subscription
See Chapter 8 for an example of a J2EE application that uses durable subscriptions. See Section A.1, "Durable Subscriptions," for an example of a client application that uses durable subscriptions.
5.2.2 Using JMS API Local Transactions
You can group a series of operations together into an atomic unit of work called a transaction. If any one of the operations fails, the transaction can be rolled back, and the operations can be attempted again from the beginning. If all the operations succeed, the transaction can be committed.
In a JMS client, you can use local transactions to group message sends and receives. The JMS API
rollbackmethods that you can use in a JMS client. A transaction commit means that all produced messages are sent and all consumed messages are acknowledged. A transaction rollback means that all produced messages are destroyed and all consumed messages are recovered and redelivered unless they have expired (see Section 5.1.4, "Allowing Messages to Expire").
A transacted session is always involved in a transaction. As soon as the
rollbackmethod is called, one transaction ends and another transaction begins. Closing a transacted session rolls back its transaction in progress, including any pending sends and receives.
In an Enterprise JavaBeansTM component, you cannot use the
Session.rollbackmethods. Instead, you use distributed transactions, which are described in Chapter 6.
You can combine several sends and receives in a single JMS API local transaction. If you do so, you need to be careful about the order of the operations. You will have no problems if the transaction consists of all sends or all receives or if the receives come before the sends. But if you try to use a request-reply mechanism, whereby you send a message and then try to receive a reply to the sent message in the same transaction, the program will hang, because the send cannot take place until the transaction is committed. 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.
It is also important to note that the production and the consumption of a message cannot both be part of the same transaction. The reason is that the transactions take place between the clients and the JMS provider, which intervenes between the production and the consumption of the message. Figure 5.3 illustrates this interaction.
Figure 5.3 Using JMS API Local Transactions
The sending of one or more messages to a queue by Client 1 can form a single transaction, because it forms a single set of interactions with the JMS provider. Similarly, the receiving of one or more messages from the queue by Client 2 also forms a single transaction. But because the two clients have no direct interaction, no transactions take place between them. Another way of putting this is that the act of producing and/or consuming messages in a session can be transactional, but the act of producing and consuming a specific message across different sessions cannot be transactional.
This is the fundamental difference between messaging and synchronized processing. Instead of tightly coupling the sending and receiving of data, message producers and consumers use an alternative approach to reliability, one that is built on a JMS provider's ability to supply a once-and-only-once message delivery guarantee.
When you create a session, you specify whether it is transacted. The first argument to the
createTopicSessionmethods is a
booleanvalue. A value of
truemeans that the session is transacted; a value of
falsemeans that it is not transacted. The second argument to these methods is the acknowledgment mode, which is relevant only to nontransacted sessions (see Section 5.1.1, "Controlling Message Acknowledgment"). If the session is transacted, the second argument is ignored, so it is a good idea to specify
0to make the meaning of your code clear. For example:topicSession = topicConnection.createTopicSession(true, 0);
rollbackmethods for local transactions are associated with the session, you cannot combine queue and topic operations in a single transaction. For example, you cannot receive a message from a queue and then publish a related message to a topic in the same transaction, because the
TopicPublisherare associated with a
TopicSession, respectively. You can, however, receive from one queue and send to another queue in the same transaction, assuming that you use the same
QueueSessionto create the
QueueSender. You can pass a client program's session to a message listener's constructor function and use it to create a message producer, so that you can use the same session for receives and sends in asynchronous message consumers. For an example of the use of JMS API local transactions, see Section A.2, "Transactions."
This Tutorial contains information on the 1.3.1 version of the Java 2 Platform, Enterprise Edition.
Copyright © 2002 Sun Microsystems, Inc. All rights reserved.