High Performance Message Processing with BMT Message-Driven Beans and Spring


Abstract

Asynchronous message processing is an important use case for many J2EE applications, so it is important to ensure that the required quality of service is handled properly by applications, and in many cases this could lead to the use of expensive distributed (XA) transactions. However, in this article we propose that it is possible to guarantee the same quality of service without using transactions for message retrieval. Moreover, our solution structures the various aspects of message processing using Spring's AOP framework.

Introduction

Message-driven beans (MDBs) have become a cornerstone of almost any enterprise Java project since their introduction in the EJB 2.0 specification. MDBs have a simple and clean API, and you don't need to create or generate home and local/remote interfaces, all contributing to the fast and wide acceptance of MDBs by the Java development community. As you know, a significant part of almost any large J2EE project is concerned with integration with other systems. A preferable way to integrate different systems is by using the enterprise queues and topics infrastructure provided by message-oriented middleware (MOM), and all J2EE servers are required to have their own JMS-compatible MOM implementation.

It is likely that the lion's share of enterprise J2EE projects are dependent on receiving and/or sending JMS messages. Therefore, it is extremely important for J2EE architects and developers to be familiar with different methods of message consuming and producing because this could ultimately dictate project success or failure. Recently, some alternatives to the MDB model have started to emerge, such as Spring's and ActiveMQ's message-driven POJOs. Besides being an interesting and useful technology by itself, the current MDB model is well established, simple enough, and widely adopted in J2EE community.

This article concentrates on the traditional, MDB-based consuming model. It shows how such a scenario can be refactored to use a more performant non-transactional message retrieval, while at the same time assuming the business case requires a once-and-only-once quality of service (QoS); in other words, the business code should process messages no more than once, and no messages should be lost. This is the most strict QoS and the most interesting one to implement.

Traditional Message Consumption Model

It's safe to say that in most J2EE projects, whenever the use case of consuming JMS messages arises, MDBs are going to be used. Besides the most trivial cases, when it's acceptable from the business perspective to lose incoming messages and/or process message duplicates, these MDBs are deployed with the container-managed transaction (CMT) demarcation model and the transactional attribute RequiresNew. In order for these settings to work, the MDB should be configured to use a transactional QueueConnectionFactory (or TopicConnectionFactory). In such a setup the message consumption process can be described in the following steps:

  1. The J2EE container starts a JTA transaction.
  2. The XAResource of the queue/topic that the MDB is listening on is enlisted in the transaction.
  3. A message is consumed from the queue/topic and passed to the onMessage() method of the MDB.
  4. The MDB then processes the message. Any transactional resource that the MDB will use while processing the message (such as database, other queues or topics, and JCA adapters) will be enlisted in the same JTA transaction that started in Step 1 (of course, only if the resources support XA).
  5. If processing is successful, which is indicated by the onMessage() call returning without exception, then the J2EE container will execute a two-phase commit on all resources enlisted in the JTA transaction. Because one of the resources enlisted in the JTA transaction is the destination from which the JMS message was consumed, upon a successful commit of the JTA transaction, the message will be removed from the queue/topic.
  6. If processing is unsuccessful, which is indicated by the onMessage() call throwing a RuntimeException, then the J2EE container will execute a rollback on the XAResource representing the destination from which the JMS message was consumed in Step 3, and therefore the message will stay in the queue/topic and potentially be redelivered later. Also in this case the MDB instance will be destroyed and removed from the processing pool.

As you can see, the process of transactional message consumption is very robust. Such a processing model guarantees a once-and-only-once QoS; in other words either the message will be successfully processed just once, or it's not going to be processed at all (potentially removed from the queue after a predefined number of retries or time to live exceeded or moved into a dead message queue).

Pages: 1, 2, 3, 4, 5

Next Page ยป