Distributed Transaction Patterns
This tech tip explores some patterns for distributed transactions.
Suppose you have two processes (A and B) on two different machines. How can you ensure reliability of tasks that span these two processes? In other words: how can you make sure that there is no partial work done by A (but not by B) or the other way around. Depending on what you want and the nature of your application, there are different ways of doing this. This tech tip explains the most common solutions.
**SOLUTION 1: Request/reply with transaction propagation
SOLUTION 2: JMS with XA drivers
SOLUTION 3: JMS with transaction propagation
SOLUTION 4: TCC
SOLUTION 1: Request/reply with transaction propagation**
This solution applies to RMI-IIOP or web service (SOAP) scenarios.
In this approach, you would do the following:
Use Transactions in each process.
A starts a transaction before calling B.
Process A ships the propagation of the transaction along with each remote call to B.
B imports the transaction before processing the call.
B processes the call.
B terminates the imported transaction and returns the Extent to A.
A adds the extent to its local transaction.
A instructs commit or rollback of the distributed transaction.
This ensures that both A and B either commit or rollback the same way.
Another approach would be to use JMS, as explained next.
SOLUTION 2: JMS with XA drivers
In this approach, the calls from A to B are done by JMS messages. This is done as explained below.
To send a message to B, A does the following:
A starts a transaction.
A sends a JMS message to B, and uses XA JMS drivers to do so.
A commits the transaction.
On the other side, B receives a message as follows:
B starts a transaction (or uses the Atomikos JMS session to do this automatically).
B consumes the JMS message.
B commits the transaction (or lets the Atomikos JMS session do this).
This pattern also ensures reliability, but should be used only under the following conditions:
The JMS is used in persistent mode.
A delay is acceptable between processing at A and processing at B.
When A sends the message to B, it is already certain that the message can be processed at B.
A does not expect a reply from B before committing its transaction.
The first condition is required to avoid message loss after A commits. The second condition is needed because JMS is asynchronous in nature. The third condition ensures that B will eventually be able to process the message. The fourth condition is due to the fact that JMS can’t receive a reply to a message that is sent within the same transaction.
For shared database situations between A and B: make sure to send (from A to B) via a different AtomikosConnectionFactoryBean, or (alternatively) make sure to send to B in a subtransaction. Otherwise, B might receive the message before A’s database updates are committed… Also see Documentation.CommitOrderingWithJms for details.
SOLUTION 3: JMS with transaction propagation
This approach works similar to the first (RMI-IIOP) approach, except that JMS messages are used instead of RMI calls. In this case, the JMS messages can be either persistent or non-persistent, and the acknowledgement mode should NOT be transactional (meaning that non-XA, regular JMS drivers should be used and messages should be sent with either AUTO ACKNOWLEDGE or EXPLICIT ACKNOWLEDGE settings. If this is true then the following will work:
Use Transactions in each process.
A starts a transaction before sending a message to B.
Process A ships the propagation of the transaction along with each JMS message to B.
B imports the transaction before processing the message.
B processes the message.
B terminates the imported transaction and returns the Extent to A in a reply message.
A adds the extent to its local transaction.
A instructs commit or rollback of the distributed transaction.
SOLUTION 4: TCC
TCC is the most loosely-coupled solution with virtually guaranteed operability for Service Oriented Architectures. If you want to know more then checkout TransactionsForSOA.