Message delivery occurs in two hops: the first hop takes the message from the producer to a physical destination on the broker; the second hop takes the message from that destination to the consumer. Thus, a message can be lost in one of three ways: on its hop from the producer to the broker, on its hop from the broker to the consumer, and while it’s in broker memory (if the broker fails). Reliable delivery guarantees that delivery will not fail in any of these ways.
Two mechanisms are used to ensure reliable delivery:
The client can use acknowledgments or transactions to make sure that message production and consumption is successful.
The broker can store messages in a persistent data store so that if the broker fails before the message is consumed, the broker, upon recovery, can retrieve the stored copy of the message and retry the operation.
The following sections describe these two aspects of ensuring reliability.
Reliable delivery only applies to messages for which the JMSDeliveryMode message header field indicates a persistent message.
Acknowledgements are messages sent between a client and the message service to ensure reliable delivery of messages. Acknowledgements are used differently for producers and for consumers.
In the case of message production, the broker confirms that it has received the message, placed it in its destination, and stored it persistently. The producer’s send() method blocks until it receives this broker acknowledgement. Broker acknowledgements are transparent to the client when persistent messages are sent.
In the case of message consumption, the client acknowledges that it has received delivery of a message from a destination and consumed it, before the broker can delete the message from that destination. JMS specifies different client acknowledgement modes that represent different degrees of reliability.
In the AUTO_ACKNOWLEDGE mode, the session automatically acknowledges each message consumed by the client. The session thread blocks, waiting for the broker to confirm that it has processed the client acknowledgement for each consumed message.
In the CLIENT_ACKNOWLEDGE mode, the client explicitly acknowledges after one or more messages have been consumed by calling the acknowledge() method of a message object. This causes the session to acknowledge all messages that have been consumed by the session since the previous invocation of the method. The session thread blocks, waiting for the broker to confirm that it has processed the client acknowledgement.
Message Queue extends this mode by providing a method that allows a client to acknowledge receipt of one message only.
In DUPS_OK_ACKNOWLEDGE mode, the session acknowledges after a specified number of messages (default is 10) have been consumed. The session thread does not block waiting for the broker to confirm it has processed the client acknowledgement, because no broker confirmation is required in this mode. Although this mode guarantees that no message will be lost, it does not guarantee that no duplicate messages will be received, hence its name: DUPS_OK.
For clients that are more concerned with performance than reliability, the Message Queue service extends the JMS API by providing a NO_ACKNOWLEDGE mode. In this mode, the broker does not track client acknowledgements, so there is no guarantee that a message has been successfully processed by the consuming client. Choosing this mode may give you better performance for non persistent messages that are sent to non-durable subscribers.
A transaction is a way of grouping the production and/or consumption of one or more messages into an atomic unit. The client and broker acknowledgement process described above applies, as well, to transactions. In this case, however, when a transaction commits, it implicitly performs the relevant broker or client acknowledgements. You cannot have an end-to-end transaction encompassing both the production and consumption of the same message.
The JMS specification supports both local and distributed transactions, as described below.
A session can be configured as transacted, and the JMS API provides methods for initiating, committing, or rolling back local transactions.
As messages are produced or consumed within a local transaction, the message service tracks the various sends and receives, completing these operations only when the JMS client issues a call to commit the transaction. If a particular send or receive operation within the transaction fails, an exception is raised. The client code can handle the exception by ignoring it, retrying the operation, or rolling back the entire transaction. When a transaction is committed, all its operations are completed. When a transaction is rolled back, all successful operations are cancelled.
The scope of a local transaction is always a single session. That is, one or more producer or consumer operations performed in the context of a single session can be grouped into a single local transaction.
The JMS specification also supports distributed transactions. That is, the production and consumption of messages can be part of a larger, distributed transaction that includes operations involving other resource managers, such as database systems. A distributed transaction manager, like the one supplied by the Sun Java System Application Server, must be available to support distributed transactions.
In distributed transactions, the distributed transaction manager tracks and manages operations performed by multiple resource managers (such as a message service and a database manager) using a two-phase commit protocol defined in the Java Transaction API (JTA), XA Resource API Specification. In the Java world, interaction between resource managers and a distributed transaction manager are described in the JTA specification.
Support for distributed transactions means that messaging clients can participate in distributed transactions through the XAResource interface defined by JTA. This interface defines a number of methods used in implementing two-phase commit. While the API calls are made on the client side, the JMS message service tracks the various send and receive operations within the distributed transaction, tracks the transactional state, and completes the messaging operations only in coordination with a distributed transaction manager—provided by a Java Transaction Service (JTS). As with local transactions, the client can handle exceptions by ignoring them, retrying operations, or rolling back an entire distributed transaction.
Message Queue supports distributed transactions only when it is used as a JMS provider in a Java Enterprise Edition (Java EE) application server. For additional information on how to use distributed transactions, please consult the Java EE documentation furnished by your application server provider.
The other aspect of reliability is ensuring that the broker does not lose persistent messages before they are delivered to consumers. This means that when a message reaches its physical destination, the broker must place it in a persistent data store. If the broker fails for any reason, it can recover the message later and deliver it to the appropriate consumers.
The broker must also persistently store durable subscriptions. Otherwise, in case of failure, it would not be able to deliver messages to durable subscribers who become active after a message has arrived in a topic destination.
Messaging applications that want to guarantee message delivery must specify messages as persistent and deliver them either to topic destinations with durable subscribers or to queue destinations.
Chapter 3, The Message Queue Broker describes the default message store supplied by the Message Queue service and how an administrator can set up and configure an alternate store.