Spring的分布式事务实现-使用和不使用XA
Posted Java丶Python丶3S小学堂
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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中监听一个消息,进行一系列操作后更新数据库,大致流程如下:
Start messaging transaction
Receive message
Start database transaction
Update database
Commit database transaction
Commit messaging transaction
如果在第4步发生错误:
Start messaging transaction
Receive message
Start database transaction
Update database, fail!
Roll back database transaction
Roll back messaging transaction
这时候,数据的操作会被回滚,消息也会被重新放回消息中间件中,然后再重新触发这个方法,开始一个新的事务。这样就保证了数据的原子性,也就是都提交成功,或都失败。
但是,这里所说的原子性,只有在使用XA的情况下才能保证。如果我们使用XA,虽然这里有database transaction
和messaging transaction
,但是XA会保证他们在一个事务中提交。
7种实现方式
使用XA和两阶段提交
上面说了,使用XA,可以保证database transaction
和messaging 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方式的配置,可以用如下方式来配置:
|
|
这里设置了activeMQ的BrokerService,给他设置一个数据库的代理存储。然后在使用jmsTemplate
时,启用它的sessionTransacted
。
如果要使用其他消息中间件,或其他资源,需要其提供这种方式才能使用。而且,还需要考虑使用数据库作为存储引起的性能改变。
最大努力一次提交
为了说明这种方式,还是看之前的实例,即在一个系统中使用数据库和消息中间件,业务流程如下:
Start messaging transaction
Receive message
Start database transaction
Update database
Commit database transaction
Commit messaging transaction
在这里,有两个事务,分别是DB的和JMS的事务,事务的开启和提交都是相互独立的。我们依次提交这两个事务,只要第二个事务顺利提交,整个方法就能够保证数据的一致性。实际上,在绝大多数情况下,只要数据库和MQ能够正常访问,这也确实能够保证。所以,这种方式就叫’最大努力’一次提交。(两个事务都是一阶段提交)。
使用这种方式,事物提交的顺序是非常重要的。假设在提交messaging transaction
的时候发生错误,这时数据库的事务已经提交,无法回滚,但是消息的事务被回滚,那么这一条消息会被重新放回队列中,该业务方法会被再次触发,再次在一个新的事务中处理。但是,这时数据的处理已经完成,只是最后JMS的事物提交出错,那么就需要通过防止重复提交的方式,来避免数据库的再次处理。修改后的流程如下:
Start messaging transaction
Receive message
if (! duplicate trigger) {
Start database transaction
Update database
Commit database transaction
}
Commit messaging transaction
也就是在处理重复触发的方法的时候,略过DB操作,直接消费消息并提交。
所以,我们必须保证messaging transaction
的提交放在后面,才能够保证数据最终的一致性。
这种方式虽然保险在两个数据资源上分别使用一次提交,但是,对于成熟的消息中间件来说,读取消息后,提交事务(也就是对该消息的消费发送确认)发生错误的概率应该很小,比如在读取消息后、在确认之前MQ服务器发生错误或网络故障。即使出错,这个消息也不会丢失,我们也可以通过其他方式处理重复提交。
在另一篇文章 REST微服务的分布式事务实现-基于消息中间件 中有专门针对这种方式有详细介绍。
Spring和message-driven POJOs
(这个不知道该怎么翻译了。。。)
上面说的共享资源的方式是JMS使用DB作为存储,而这种方式是配置JMS的链接,让它的事物和DB的事物同步。它通过这种方式配置:
|
|
它使用TransactionAwareConnectionFactoryProxy
配置JMS的ConnectionFactory
并通过AOP来实现JMS的事务和DB的事务同步,也就是DB事务的提交也会触发JMS事务的提交。
这种方式应该是同时使用MQ和DB时实现分布式事务的最好的方式,既不会影响MQ数据存储的性能,也能通过一次提交实现对两个资源的同步。
链式事务管理
这种方式也是Spring提供的,可以将两个或多个数据库资源的事务串联到一起,来公用一个TransactionManager
来实现对多个资源的事务。配置方式如下:
|
|
可以看出,它是针对多个数据库资源实现事务。使用这种方式时,在Spring事务提交的时候,它会依次(按照定义的顺序逆序)调用里面的多个Connection的commit()
方法,如果业务方法出错,就会依次调用rollback()
方法。
既然是依次执行,就还是有可能会出现先前的提交成功,之后的提交失败,所以还是会有事务失败的可能。
如何选择
有那么多种方式,该如何选择?首先,你需要对这些实现方式、背后的原理、实现逻辑都有一个清晰的认识,然后再根据自己的具体情况,选择合适的实现方式。
Spring的团队推荐使用最大努力一次提交
的方式(在做这些那篇文章的时候,也就是2009年。不过,似乎现在Spring也推荐这种方式)。也就是使用一个事务管理器,依次提交两个资源的事物。
相关文章:
以上是关于Spring的分布式事务实现-使用和不使用XA的主要内容,如果未能解决你的问题,请参考以下文章