user3444718
user3444718

Reputation: 1605

Transaction handling Rabbit MQ and Spring AMQP

I am trying to understand few things here. My requirement was I want to store record in db and want to send message to queue and then lets say in same method if it throws some exception I don't want to send message and don't want to commit db transaction. Now I thought of using spring transaction but since two different resources, thought of using JTA using some atomikos to synchronize resources - but again I read RMQ do not support 2PC or XA etc. Anyway I went ahead and tried first without adding atomikos, all I did is made sure that my channel transacted and @Transaction annotation took care, see below sample code - I didn't added anything special in pom.

Now my question is how is this working, how is this different from 2PC - and what can go wrong with approach and what situations can mess up eventual consistency using this method. And surprisingly why I didn't have to use third party jta. If all is good with this - this seems to me eventual consistency guarantee when we use rmq and db using spring goodies! for microservices :)

If this is not good solution what are alternatives - I would like to avoid worker process etc if possible for eventual consistency.

@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
    RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
    rabbitTemplate.setChannelTransacted(true);
    return rabbitTemplate;
}

@GetMapping
@Transactional
public void sampleEndpoint(@RequestParam boolean throwException){
    Customer a=new Customer();
    a.setCustomerName("XYZ");
    customerRepository.save(a);
    rabbitTemplate.convertAndSend("txtest","Test");
    if(throwException)
    throw new RuntimeException();
} 

I used postgres dependency for above example using spring boot 1.5.7

Upvotes: 4

Views: 7779

Answers (2)

Rémi Bantos
Rémi Bantos

Reputation: 1967

Regarding the "How is it working" question, this quote from spring-amqp documentation clarifies:

If there is already a transaction in progress when the framework is sending or receiving a message, and the channelTransacted flag is true, then the commit or rollback of the messaging transaction will be deferred until the end of the current transaction. If the channelTransacted flag is false, then no transaction semantics apply to the messaging operation (it is auto-acked).

My understanding is that, for your use case, you do not even need to configure a ChainedTransactionManager in order to implement Best Effort 1PC. @Transactional will be enough and the rabbit tx will commit right after the DB tx.

Upvotes: 2

Gary Russell
Gary Russell

Reputation: 174484

I suggest you read Dave Syer's article: Distributed transactions in Spring, with and without XA.

You need to start the Rabbit transaction before the database transaction so the rabbit transaction is synchronized with the DB transaction and commits very soon after the DB tx and rolls back if the DB tx rolls back.

There is a small possibility that the DB tx commits successfully but the Rabbit tx rolls back. This is called "Best Effort 1PC" in the article. You need to deal with the small possibility of duplicate messages.

You don't show all your configuration but it appears your Rabbit tx will commit before the DB, which is probably not what you want.

Upvotes: 7

Related Questions