Spring的分布式事务实现-使用和不使用XA(翻译)

栏目: 服务器 · 发布时间: 7年前

内容简介:Spring的分布式事务实现-使用和不使用XA(翻译)

说到 Java 实现分布式系统,网上的很多资料很多都会引用Java World上的一篇文章 Distributed transactions in Spring, with and without XA 。虽然这篇文章发布于2009年,现在的分布式系统也比那时候复杂了很多,但是对于分布式事务的实现来说,其实现方法也离不开这篇文章中介绍的方法。所以这里对这篇文章做一个翻译,希望能对大家理解分布式事务有所帮助。翻译的过程中,我将根据我的理解来表述原文的意思,而不是一句一句的直接翻译。因为本人水平有限,如果表述的有不恰当甚至错误的地方,希望读者能及时指出。

有关Spring事务和JTA的介绍,可以参考另一篇文章 REST微服务的分布式事务实现-分布式事务以及JTA介绍

在Java系统中实现分布式事务,通常都会使用JTA,而Spring为我们提供了一个通用的事务抽象来使用JTA。由于JTA遵从XA规范,也就是两阶段提交。由于实现XA的成本非常高,所以我们就可以在有些情况下来使用非XA的方式实现分布式事务。

这篇文章中,提出了7中实现分布式事务的方法,有使用XA的方法,也有不使用XA的方法。这几种方法的顺序是以安全性和可靠性来排序。第一种方法就是最安全可靠的,能完全保证数据的一致性和原子性。越往后,可靠性和数据的一致性就会降低。但是,性能却基本上是相反,越往后的实现方法,系统的性能会越高。

数据的原子性

本文所说的分布式事务,说的是一个系统需要操作多个数据资源的时候,实现的事务,而不是多个服务之间实现分布式事务。而这个’资源’需要支持事务,就需要它实现 rollback() , commit() 方法。而支持XA的资源,就需要实现 XAResource 接口里面定义的方法,否则就无法实现两阶段提交。

在一个典型的例子中,一个系统需要同时操作数据库和MQ,比如,从JMS中监听一个消息,进行一系列操作后更新数据库,大致流程如下:

  1. Start messaging transaction
  2. Receive message
  3. Start database transaction
  4. Update database
  5. Commit database transaction
  6. Commit messaging transaction

如果在第4步发生错误:

  1. Start messaging transaction
  2. Receive message
  3. Start database transaction
  4. Update database, fail!
  5. Roll back database transaction
  6. Roll back messaging transaction

这时候,数据的操作会被回滚,消息也会被重新放回消息中间件中,然后再重新触发这个方法,开始一个新的事务。这样就保证了数据的原子性,也就是都提交成功,或都失败。

但是,这里所说的原子性,只有在使用XA的情况下才能保证。如果我们使用XA,虽然这里有 database transactionmessaging transaction ,但是XA会保证他们在一个事务中提交。

7种实现方式

使用XA和两阶段提交

上面说了,使用XA,可以保证 database transactionmessaging transaction 在一个事务中提交,这是由XA的两阶段提交来实现的。也正是因为XA事务的提交分为两个阶段提交,才会产生性能问题。首先,它为了两阶段提交需要做很多额外的操作;其次,因为在一个事务中,会通过锁等机制来保证隔离性。现在有2个数据库的操作在一个事务中,这就使得锁的时间大大加长。

如果使用Spring的事务,它对JTA进行了抽象,我们就可以在不修改业务代码的情况下,在Spring本地事物和JTA事务之前方便的切换。

使用XA和一阶段优化

有些情况下,如果只有一个资源,如只操作一个数据库,又使用了JTA,那么一些应用服务器会针对这种情况做优化,使用一阶段提交来使用XA。

XA和最后资源博弈

不知道这样翻译对不对。这种方式的意思是说,如果一个系统需要访问2个资源,即使它们都支持XA的事务,但是,只在一个资源上启用XA,提交的时候,将没有启用XA的资源的提交放在最后。这样,用非XA的事物来控制前面的XA的事物提交。这样就在一定程度上避免的事务造成的资源竞争。

上面三种都是属于使用XA实现,接下来的几种方式,就是不是用XA的方式。

共享资源

这种方式,简单来说,就是对于2个资源,在底层实际上使用同一个资源。例如,一个系统使用一个DB一个MQ,对于一些消息中间件,它支持使用数据库来作为它的存储层。这样,我们对JMS和DB都使用底层的同一个资源,这样就能使用同一个资源上的事务。如ActiveMQ就支持使用数据库作为存储。

如果使用以前的XML方式的配置,可以用如下方式来配置:

<beanid="connectionFactory"class="org.apache.activemq.ActiveMQConnectionFactory"
  depends-on="brokerService">
  <propertyname="brokerURL"value="vm://localhost?async=false"/>
</bean>

<beanid="brokerService"class="org.apache.activemq.broker.BrokerService"init-method="start"
  destroy-method="stop">
    ...
  <propertyname="persistenceAdapter">
    <beanclass="org.apache.activemq.store.jdbc.JDBCPersistenceAdapter">
      <propertyname="dataSource">
        <beanclass="com.springsource.open.jms.JmsTransactionAwareDataSourceProxy">
          <propertyname="targetDataSource"ref="dataSource"/>
          <propertyname="jmsTemplate"ref="jmsTemplate"/>
        </bean>
      </property>
      <propertyname="createTablesOnStartup"value="true"/>
    </bean>
  </property>
</bean>

这里设置了activeMQ的BrokerService,给他设置一个数据库的代理存储。然后在使用 jmsTemplate 时,启用它的 sessionTransacted

如果要使用其他消息中间件,或其他资源,需要其提供这种方式才能使用。而且,还需要考虑使用数据库作为存储引起的性能改变。

最大努力一次提交

为了说明这种方式,还是看之前的实例,即在一个系统中使用数据库和消息中间件,业务流程如下:

  1. Start messaging transaction
  2. Receive message
  3. Start database transaction
  4. Update database
  5. Commit database transaction
  6. Commit messaging transaction

在这里,有两个事务,分别是DB的和JMS的事务,事务的开启和提交都是相互独立的。我们依次提交这两个事务,只要第二个事务顺利提交,整个方法就能够保证数据的一致性。实际上,在绝大多数情况下,只要数据库和MQ能够正常访问,这也确实能够保证。所以,这种方式就叫’最大努力’一次提交。(两个事务都是一阶段提交)。

使用这种方式,事物提交的顺序是非常重要的。假设在提交 messaging transaction 的时候发生错误,这时数据库的事务已经提交,无法回滚,但是消息的事务被回滚,那么这一条消息会被重新放回队列中,该业务方法会被再次触发,再次在一个新的事务中处理。但是,这时数据的处理已经完成,只是最后JMS的事物提交出错,那么就需要通过防止重复提交的方式,来避免数据库的再次处理。修改后的流程如下:

  1. Start messaging transaction
  2. Receive message
  3. if (! duplicate trigger) {
  4. Start database transaction
  5. Update database
  6. Commit database transaction
  7. Commit messaging transaction

也就是在处理重复触发的方法的时候,略过DB操作,直接消费消息并提交。

所以,我们必须保证 messaging transaction 的提交放在后面,才能够保证数据最终的一致性。

这种方式虽然保险在两个数据资源上分别使用一次提交,但是,对于成熟的消息中间件来说,读取消息后,提交事务(也就是对该消息的消费发送确认)发生错误的概率应该很小,比如在读取消息后、在确认之前MQ服务器发生错误或网络故障。即使出错,这个消息也不会丢失,我们也可以通过其他方式处理重复提交。

在另一篇文章 REST微服务的分布式事务实现-基于消息中间件 中有专门针对这种方式有详细介绍。

Spring和message-driven POJOs

(这个不知道该怎么翻译了。。。)

上面说的共享资源的方式是JMS使用DB作为存储,而这种方式是配置JMS的链接,让它的事物和DB的事物同步。它通过这种方式配置:

<beanid="connectionFactory"
  class="org.springframework.jms.connection.TransactionAwareConnectionFactoryProxy">
  <propertyname="targetConnectionFactory">
    <beanclass="org.apache.activemq.ActiveMQConnectionFactory"depends-on="brokerService">
      <propertyname="brokerURL"value="vm://localhost"/>
    </bean>
  </property>
  <propertyname="synchedLocalTransactionAllowed"value="true"/>
</bean>

它使用 TransactionAwareConnectionFactoryProxy 配置JMS的 ConnectionFactory 并通过AOP来实现JMS的事务和DB的事务同步,也就是DB事务的提交也会触发JMS事务的提交。

这种方式应该是同时使用MQ和DB时实现分布式事务的最好的方式,既不会影响MQ数据存储的性能,也能通过一次提交实现对两个资源的同步。

链式事务管理

这种方式也是Spring提供的,可以将两个或多个数据库资源的事务串联到一起,来公用一个 TransactionManager 来实现对多个资源的事务。配置方式如下:

<beanid="transactionManager"class="com.springsource.open.db.ChainedTransactionManager">
  <propertyname="transactionManagers">
    <list>
      <bean
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <propertyname="dataSource"ref="dataSource"/>
      </bean>
      <bean
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <propertyname="dataSource"ref="otherDataSource"/>
      </bean>
    </list>
  </property>
</bean>

可以看出,它是针对多个数据库资源实现事务。使用这种方式时,在Spring事务提交的时候,它会依次(按照定义的顺序逆序)调用里面的多个Connection的 commit() 方法,如果业务方法出错,就会依次调用 rollback() 方法。

既然是依次执行,就还是有可能会出现先前的提交成功,之后的提交失败,所以还是会有事务失败的可能。

如何选择

有那么多种方式,该如何选择?首先,你需要对这些实现方式、背后的原理、实现逻辑都有一个清晰的认识,然后再根据自己的具体情况,选择合适的实现方式。

Spring的团队推荐使用 最大努力一次提交 的方式(在做这些那篇文章的时候,也就是2009年。不过,似乎现在Spring也推荐这种方式)。也就是使用一个事务管理器,依次提交两个资源的事物。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

深度探索Linux操作系统

深度探索Linux操作系统

王柏生 / 机械工业出版社 / 2013-10-15 / 89.00

《深度探索linux操作系统:系统构建和原理解析》是探索linux操作系统原理的里程碑之作,在众多的同类书中独树一帜。它颠覆和摒弃了传统的从阅读linux内核源代码着手学习linux操作系统原理的方式,而是基于实践,以从零开始构建一个完整的linux操作系统的过程为依托,指引读者在实践中去探索操作系统的本质。这种方式的妙处在于,让读者先从宏观上全面认清一个完整的操作系统中都包含哪些组件,各个组件的......一起来看看 《深度探索Linux操作系统》 这本书的介绍吧!

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具