Let's consider the following context:
2 spring integration channels, they are each in separate database transactions. At the end of the first transaction, a message is put into the second channel. In the first channel, elements are created in the database that is later consumed by the corresponding message that has been sent from the first channel to the second channel.
To make sure that the transaction from channel 1 is fully committed before the second channel is triggered our subclass of the JpaTransactionManager is registering a TransactionSynchronization in the prepareForCommit method it overrides from the JpaTransactionManager
The flow (channel 1) looks like this:
Do all the message processing and database handling
Last step of the flow registers a TransactionSynchronization that does a MessageChannel.send in the afterCommit phase to send the message to channel 2
My understanding is that at the time the message is sent to the second channel (in afterCommit) all changes that have been done in the database transaction of channel 1 are flushed and committed.
Now the second channel does some work (like an MQ PUT) and later updates an entry that was created in the first flow. We have now observed that the repository method returned no entry in the database, but it is visible in the table at a later point. Other entries that were also created in the transaction of the first channel however are visible. This happens only once every few thousand messages, normally they are there but sometimes they are not visible for the second channel a few milliseconds after the transaction has been committed by channel 1.
I have created an image that should illustrate it:
The Chain 1 is the first chain that consists of multiple ServiceActivators that perform database work, a splitter that generates more messages and then another ServiceActivator that I named SENDER which registers the TransactionSynchronization that (so my understanding) should send the for example 3 generated messages to chain 2 after the red transaction is fully committed and therefore before the blue transaction begins.
One thing I have noticed is that the entries that were sometimes present and sometimes not are all in the one method that (not on purpose) uses javax.transaction.Transactional instead of org.springframework.transaction.annotation.Transactional. However, we are using spring core 5.0.8.RELEASE and in other questions I have seen that this should make 0 difference since spring 4.2.x.
I don't think the afterCommit is the right place to send messages downstream.
There should be just enough to have a Service Activator for POJO method marked with the #Transactional. This way a transaction is going to start and finish exactly around this method call. The result of the method is going to be sent to the output channel already, exactly after that transaction is committed.
UPDATE
The best way to achieve your requirements is a <gateway> around your Chain1. This way the TX is going to be committed over there before producing reply to the Chain2 from the gateway.
With the TransactionSynchronization::afterCommit there is no guarantee that TX is going to be committed on DB when QueueChannel is ready for polling messages. Although you can use JdbcChannelMessageStore for transactional storage of messages. This way they are not going to be visible until TX commit in DB.
See more about <gateway> in Docs: https://docs.spring.io/spring-integration/docs/current/reference/html/messaging-routing-chapter.html#_calling_a_chain_from_within_a_chain
Related
I'm developing a system that contains multiple microservices operating on the same database, which isn't ideal but is done for the sake of legacy support. As we refactored the old system we split old, transactional functionality into separate microservices, which led us to have distributed transactions now that must be handled in some way. We're using Spring boot.
As it usually goes, we have a microservice A calling microservices B and then C, we need a way to rollback transaction B if transaction C throws an error.
I've read on 2PC and Saga approaches, I was wondering if there is a way to implement a somewhat simpler variation of 2PC approach. It would need to support the following functionality.
1)Microservice endpoint is called, it calls a #Transactional service which creates a transaction. A special parameter is passed that tells the TransactionManager to keep the transaction "hanging" for some time (say, 5 seconds)
2)In those 5 seconds, another call can be made that either confirms or rolls back the transaction
3)If the time elapses (times out), default handling for the transaction is applied (either rollback or commit)
4)If the special parameter isn't passed in, transactions behave as if they are not distributed
5)API calls are still asynchronous
Simple example:
1)Service A endpoint "createResource" is called with parameter "hangTransaction=true"
2)Service A returns status 200
3)Service B endpoint "registerResource" is called
4)Service B returns status 500
5)Service A endpoint "createResource" is called with parameter "transactionValid=false"
6)Service A rolls the transaction back and returns status 200
7)There are no changes to DB after this
There would of course be additional parameters sent (transaction ID for example), the example is simplified.
Is there some way to control the TransactionManager in a way that allows the transaction to persist between API calls?
I am using Spring AMQP (RabbitMQ implementation) and I am trying to propagate a single transaction into multiple threads.
As an example, Let's say there are 3 queues, names X,Y,Z and first I'm obtaining a message from queue X using thread-1, and next,that message is given to thread-0, and within thread-0 message is cloned and sent to queue Y via, thread-2 and queue Z via thread-3. Thread-0 wait for the completion of both thread-3 and thread-4, to commit or rollback message. Note that's I'm using 4 threads here.
What I want is basically to handle this 3 operations,(getting message and putting it to two queues) as a single transaction. i.e. if I successfully send the message to queue Y, but fails to send it to Z, then the message sent to Y will be rollbacked and the original message will be rolled back into queue X as well.
Up to now, I have managed to propegate transaction information via threadLocals (mainly TransactionStatus and TransactionSynchronizationManager.resources) and I was able to bind these 3 operations into one transaction.
But my problem is with sending ACK/NACK to original queue X, even though I commit/rollback transaction, it only works for queues Y and Z only. The message obtained from X is always in Unacked state.
I have tries channel.basicAck(), RabbitUtils.commitIfNecessary(), approaches, but no success.
Note that I have enabled channelTransacted as well. Any help is highly appreciated.
Spring transactions are bound to a single thread. You might be able to get it to work using the RabbitMQ client directly - but you would have to share the same channel across all the threads. However, the RabbitMQ documentation strongly advises against doing that:
Channel instances must not be shared between threads. Applications should prefer using a Channel per thread instead of sharing the same Channel across multiple threads. While some operations on channels are safe to invoke concurrently, some are not and will result in incorrect frame interleaving on the wire. ...
In any case, even if you do the work on a single thread, RabbitMQ transactions are rather weak and don't guarantee much; see broker semantics.
AMQP guarantees atomicity only when transactions involve a single queue, i.e. all the publishes inside the tx get routed to a single queue and all acks relate to messages consumed from the same queue. ...
Message driven beans are wonderful Java EE technology pieces that allow you to handle and process data concurrently, one message - one task - one thread.
However sometimes there is need to keep the state of previous messages so that after collecting few or dozens of messages whole set of these messages might be processed together inside the single the same thread.
The point is MDB after receiving message should avoid next steps, somehow informs that for example next 4 messages in the JMS queue should be saved somewhere, and after that after receiving 5th message these 5 messages should be used fo some calculation together.
The question is: what kind of technology should be used to handle this scenario?
Singleton bean? IMDG (for example JBOSS Cache, Infinispan)? What is the best practise?
EDIT:
Unfortunatelly (almost) always two JMS messages are handled by two different instances of Message Driven Bean and each instance of MDB uses different instance of any session bean (stateful or stateless).
#MessageDriven(activationConfig = {...})
public class SomeMDB implements MessageListener {
#EJB AnyService service
}
In that such of case, even if AnyService is a stateful bean, each message has its own instance of SomeMDB and instance of AnyService.
Correct me if I am wrong.
My best guess would be to invoke a stateful session bean from your message driven been.
Since stateful session beans can keep their memory you can check how many messages have been received using a static counter.
Hope it helps!
If you use stateful session beans, if the EJB server goes down, we will loose all of the previous messages, which causes message loss.
Rather, In my opinion, we can achieve your requirement using single MDB + Database
We can follow the below steps:
(1.0) MDB Receives the message
(2.0) Check if the required amount (5 or 10 what ever) of messages already exists in the database
(2.1) If YES, Retrieve the existing messages from the database and process
them.
(2.2) If NO, Persist the message to the Database
P.S.: You might need to clean up the messages in the database on daily basis or after processing them successfully.
Is there any transaction isolation level available in JMS queue like in JDBC ?
More specifically in transitional mode, when a client consumes a message and has not committed yet, will the next client consume the next message? If the 1st client rolls back transition, where the message placed in the queue, I think it will be placed where it was earlier.
Short answer: no.
JMS sending is either transactional or not, which means the message is "committed" (visible for the broker and MDBs) when senders transaction commits or immediately (even if client sending transaction rollbacks later).
JMS receiving has simple on/off transactions as well: message is marked as received either when it's picked up by MDB (non-transactional) or when MDB returns without exception (transactional).
Transaction isolation is about visibility of DB table changes. JMS messages are immutable, you send it once and you cannot change it.
I am trying to write an Application that uses the JMS publish subscribe model. However I have run into a setback, I want to be able to have the publisher delete messages from the topic. The usecase is that I have durable subscribers, the active ones will get the messages (since it's more or less instantly) , but if there are inactive ones and the publisher decides the message is wrong, I want to have him able to delete the message so that the subscribers won't receive it anymore once they become active.
Problem is, I don't know how/if this can be done.
For a provider I settled on glassfish's implementation, but if other alternatives offer this functionality, I can switch.
Thank you.
JMS is a form of asynchronous messaging and as such the publishers and subscribers are decoupled by design. This means that there is no mechanism to do what you are asking. For subscribers who are active at time of publication, they will consume the message with no chance of receiving the delete message in time to act on it. If a subscriber is offline then they will but async messages are supposed to be atomic. If you proceed with design of other respondent's answer (create a delete message and require reconnecting consumers to read the entire queue looking for delete messages), then you will create a situation in which the behavior of the system differs based on whether or not a subscriber was online or not at the time a specific message/delete combination was was published. There is also a race condition in which the subscriber completes reading of the retained messages just before the publisher sends out the delete message. This means you must put significant logic into subscribers to reconcile these conditions and even more to reconcile the race condition.
The accepted method of doing this is what are called "compensating transactions." In any system where the producer and consumer do not share a single unit of work or share common state (such as using the same DB to store state) then backing out or correcting a previous transaction requires a second transaction that reverses the first. The consumer must of course be able to apply the compensating transaction correctly. When this pattern is used the result is that all subscribers exhibit the same behavior regardless of whether the messages are consumed in real time or in a batch after the consumer has restarted.
Note that a compensating transaction differs from a "delete message." The delete message as proposed in the other respondent's answer is a form of command and control that affects the message stream itself. On the other hand, compensating transactions affect the state of the system through transactional updates of the system state.
As a general rule, you never want to manage state of the system by manipulating the message stream with command and control functions. This is fragile, susceptible to attack and very hard to audit or debug. Instead, design the system to deliver every message subject to its quality of service constraints and to process all messages. Handle state changes (including reversing a prior action) entirely in the application.
As an example, in banking where transactions trigger secondary effects such as overdraft fees, a common procedure is to "memo post" the transactions during the day, then sort and apply them in a batch after the bank has closed. This allows a mistake to be reconciled before it causes overdraft fees. More recently, the transactions are applied in real time but the triggers are withheld until the day's books close and this achieves the same result.
JMS API does not allow removing messages from any destination (either queue or topic). Although I believe that specific JMX providers provide their own proprietary tools to manage their state for example using JMX. Try to check it out for your JMS provider but be careful: even if you find solution it will not be portable between different JMS providers.
One legal way to "remove" message is using its time-to-live:
publish(Topic topic, Message message, int deliveryMode, int priority, long timeToLive). Probably it is good enough for you.
If it is not applicable for your application, solve the problem on application level. For example attach unique ID to each message and publish special "delete" message with higher priority that will be a kind of command to delete "real" message with the same ID.
You have have the producer send a delete message and the consumer needs to read all messages before starting to process them.