JMS (Java Message Service) is an interfacing technology to access message servers from within your Java or J2EE application. During the last years, JMS has gained enormous popularity in the EAI (enterprise application integration) business because of its ability to provide so-called reliable messaging. This tech tip takes a closer look at what 'reliable' messaging means and how to achieve it. As we will see, it all depends on how you configure and use your JMS. But first some vocabulary.
Maybe: in this case, there is actually no guarantee. A message may disappear before it can be consumed.
At least once: a message is never lost in the system, but can be delivered to the receiver more than once (and hence be consumed more than once). This implies that the receiver needs to be programmed with this in mind.
Exactly once: a message is never lost and only delivered once. This is the real reliable messaging mode.
Whether or not messages are persisted in the JMS server.
Which acknowledgement mode is used by the receiver.
Clearly, if messages are not persisted then there is always the possibility of losing a message (if the server crashes before the message can be delivered). So in that case, you can only count on Maybe semantics.
However, even if messages are persisted there is still the possibility of losing messages (in this tech tip, 'losing' a message is somewhat broader than as defined in the JMS specification: we say that a message is lost if it fails to be processed in the DBMS of the receiver). For persistent messaging, all depends on the acknowledgement mode. The acknowledgement of a message triggers the deletion of the message on the server.
AUTOMATIC ACKNOWLEDGEMENT: this means that a message is acknowledged (deleted) as soon as the receiver gets it.
EXPLICIT ACKNOWLEDGEMENT: this means that the receiver has to explicitly tell the server when the message is no longer needed.
TRANSACTIONAL ACKNOWLEDGEMENT: if the receiver gets the messages within the scope of a JTA transaction then acknowledgement is done if and only if the transaction commits.
It is tempting to believe that it suffices to use JMS in order to achieve fully reliable messaging, but this is not true.
Let's consider the following example of where things can go wrong. Assume that a message comes in on the JMS queue (say, a payment order) and we want to process the payment in the database.
Can messages be received twice?
The answer to each question will depend on the acknowledgement mode for JMS. Again, the possible values are: automatic, explicit and transactional.
With automatic acknowledgement, the JMS message is deleted from the queue as soon as it is taken off. Clearly, this allows message loss if our program crashes in step 2. In that case, the payment order is lost forever. Hence, we have message loss.
With explicit acknowledgement, the JMS message is not deleted from the queue until our program tells it to do so. When could we tell the JMS to delete the message? If done before step 2, then a crash in 2 means message loss again. If done during step 2, then a crash will also lose the message since the database has not been changed. If done after step 3, then a crash could happen between 3 and before we can delete the message. In that case, the message will be redelivered upon restart and will be re-processed and re-posted to the database. That is: duplicates.
With transactional acknowledgement, there are two possibilities: connector-level JMS and JDBC transactions or JTA/XA. When JMS is used in transactional mode, the deletion of a message is only done if and only if the transaction commits. JDBC implies that the database is updated only if the transaction commits.
For connector-level transactions, we will have two distinct transactions: one for JMS and one for JDBC. So the question is: how should the commits be ordered to avoid message loss and duplicates?
If the JMS transaction is committed first, a crash can happen before we commit the JDBC transaction. Consequence: message loss; the payment will never be in the database.
If the JDBC transaction is committed first, a crash can happen before we commit the JMS transaction. Consequence: duplicate message; the message is not deleted from the queue and will be redelivered later. The application will re-process it and re-post it in the database.
The only remaining possibility is: joint commit of both the JMS and the JDBC transaction, and this is exactly what JTA/XA do for you. For reliable, exactly-once messaging you really need JTA/XA.