Spring AMQP 随笔 5 Transaction

0. 十多年前的电视剧,好像并没有很在意剧情的合理

希望有 ipad 原生支持 vscode, intellij 的那一天


4.1.16. Transactions

The Spring Rabbit framework has support for automatic transaction management in the synchronous and asynchronous use cases with a number of different semantics(语义).

There are two ways to signal the desired transaction semantics to the framework. In both the RabbitTemplate and SimpleMessageListenerContainer

  • there is a flag channelTransacted which, if true, tells the framework to use a transactional channel and to end all operations (send or receive) with a commit or rollback (depending on the outcome), with an exception signaling a rollback.

The channelTransacted flag is a configuration time setting. It is declared and processed once when the AMQP components are created, usually at application startup.

  • Another signal is to provide an external transaction with one of Spring’s PlatformTransactionManager implementations as a context for the ongoing operation.
    If there is already a transaction in progress when the framework is sending or receiving a message, and the channelTransacted flag is true, the commit or rollback of the messaging transaction is deferred until the end of the current transaction.
    If the channelTransacted flag is false, no transaction semantics apply to the messaging operation (it is auto-acked).

The external transaction is more dynamic in principle because the system responds to the current thread state at runtime. However, in practice,
it is often also a configuration setting, when the transactions are layered(分层) onto an application declaratively.

For synchronous use cases with RabbitTemplate, the external transaction is provided by the caller, either declaratively or imperatively(必然地) according to taste (the usual Spring transaction model).
The following example shows a declarative approach (usually preferred because it is non-invasive, 非侵入性的), where the template has been configured with channelTransacted=true:

@Transactional
public void doSomething() {
    String incoming = rabbitTemplate.receiveAndConvert();
    // do some more database processing...
    String outgoing = processInDatabaseAndExtractReply(incoming);
    rabbitTemplate.convertAndSend(outgoing);
}

If the database processing fails with an exception, the incoming message is returned to the broker, and the outgoing message is not sent.

For asynchronous use cases with SimpleMessageListenerContainer, if an external transaction is needed,
it has to be requested by the container when it sets up the listener. To signal that an external transaction is required,
the user provides an implementation of PlatformTransactionManager to the container when it is configured.

@Configuration
public class ExampleExternalTransactionAmqpConfiguration {
    @Bean
    public SimpleMessageListenerContainer messageListenerContainer() {
        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
        container.setConnectionFactory(rabbitConnectionFactory());
        container.setTransactionManager(transactionManager());
        container.setChannelTransacted(true);
        container.setQueueName("some.queue");
        container.setMessageListener(exampleListener());
        return container;
    }
}

Significantly, if the transaction fails to commit (for example, because of a database constraint error or connectivity problem),
the AMQP transaction is also rolled back, and the message is returned to the broker.
This is sometimes known as a “Best Efforts 1 Phase Commit”, and is a very powerful pattern for reliable messaging.
If the channelTransacted flag was set to false (the default) in the preceding example, the external transaction would still be provided for the listener,
but all messaging operations would be auto-acked, so the effect is to commit the messaging operations even on a rollback of the business operation.

Conditional Rollback

Prior to version 1.6.6, adding a rollback rule to a container’s transactionAttribute when using an external transaction manager (such as JDBC) had no effect.

Also, when using a transaction advice in the container’s advice chain, conditional rollback was not very useful, because all listener exceptions are wrapped in a ListenerExecutionFailedException.

The ListenerFailedRuleBasedTransactionAttribute is now provided. It is a subclass of RuleBasedTransactionAttribute,
with the only difference being that it is aware of the ListenerExecutionFailedException and uses the cause of such exceptions for the rule.
This transaction attribute can be used directly in the container or through a transaction advice.

@Bean
public AbstractMessageListenerContainer container() {
    ...
    container.setTransactionManager(transactionManager);
    RuleBasedTransactionAttribute transactionAttribute =
        new ListenerFailedRuleBasedTransactionAttribute();
    transactionAttribute.setRollbackRules(Collections.singletonList(
        new NoRollbackRuleAttribute(DontRollBackException.class)));
    container.setTransactionAttribute(transactionAttribute);
    ...
}

A note on Rollback of Received Messages

Spring AMQP has to not only rollback the transaction but also manually reject the message (sort of a nack, but that is not what the specification calls it).
The action taken on message rejection is independent of transactions and depends on the defaultRequeueRejected property (default: true).

Prior to RabbitMQ 2.7.0, such messages (and any that are unacked when a channel is closed or aborts) went to the back of the queue on a Rabbit broker.
Since 2.7.0, rejected messages go to the front of the queue, in a similar manner to JMS rolled back messages.

Previously, message requeue on transaction rollback was inconsistent between local transactions and when a TransactionManager was provided.
In the former case, the normal requeue logic (AmqpRejectAndDontRequeueException or defaultRequeueRejected=false) applied.
With a transaction manager, the message was unconditionally(绝对) requeued on rollback.
Starting with version 2.0, the behavior is consistent and the normal requeue logic is applied in both cases.
To revert to the previous behavior, you can set the container’s alwaysRequeueWithTxManagerRollback property to true.

Using RabbitTransactionManager

The RabbitTransactionManager is an alternative(替代) to executing Rabbit operations within, and synchronized with, external transactions.
This transaction manager is an implementation of the PlatformTransactionManager interface and should be used with a single Rabbit ConnectionFactory.

This strategy is not able to provide XA transactions — for example, in order to share transactions between messaging and database access.

Application code is required to retrieve(取回) the transactional Rabbit resources through ConnectionFactoryUtils.getTransactionalResourceHolder(ConnectionFactory, boolean)
instead of a standard Connection.createChannel() call with subsequent channel creation.
When using Spring AMQP’s RabbitTemplate, it will autodetect a thread-bound Channel and automatically participate in its transaction.

@Bean
public RabbitTransactionManager rabbitTransactionManager() {
    return new RabbitTransactionManager(connectionFactory);
}

Transaction Synchronization

Synchronizing a RabbitMQ transaction with some other (e.g. DBMS) transaction provides “Best Effort One Phase Commit” semantics.
It is possible that the RabbitMQ transaction fails to commit during the after completion phase of transaction synchronization.
This is logged by the spring-tx infrastructure as an error, but no exception is thrown to the calling code.
Starting with version 2.3.10, you can call ConnectionUtils.checkAfterCompletion() after the transaction has committed on the same thread that processed the transaction.
It will simply return if no exception occurred; otherwise it will throw an AfterCompletionFailedException which will have a property representing the synchronization status of the completion.

Enable this feature by calling ConnectionFactoryUtils.enableAfterCompletionFailureCapture(true); this is a global flag and applies to all threads.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值