发布网友 发布时间:2022-04-24 09:50
共1个回答
热心网友 时间:2022-04-08 01:15
Java Transaction
API和XA协议是Spring常用的分布式事务机制,不过你可以选择选择其他的实现方式。理想的实现取决于你的应用程序使用何种资源,你愿意在性能、安
全、系统稳健性、数据完整方面做出何种权衡。在这次JavaWorld大会上,来自SpringSource的David
Syer跟大家分享了Spring应用的几种事务处理机制、三种XA式、四种非XA式事务协议。
Spring框架支持Java Transaction API(JTA),这样应用就可以脱离Java EE容器,转而利用分布式事务以及XA协议。然而即使有这样的支持,XA开销是昂贵的,不稳定而且笨重不利于管理,不过一些其他的应用可以避免使用XA协议。
为了让大家对所涉及的几种分布式事务有所了解,我会分析七种事务处理模式,并
给出具体代码实现。并且从安全或者稳定性入手倒序展示,可以看看从安全、稳定性出发,如何在一般场景下,保障数据高完整性和原子性。当然随着话题的深入,
更多的说明以及*就会出现。模式也可以从运行时开销倒序展示。考虑到所有模式都是结构化或者学术性的,这一点有别于业务模型,因此我不打算展开业务用例
分析,仅仅关注每种模式其少部分代码如何工作的。
尽管只有起初的三种模式涉及到
XA协议,不过从性能角度出发,这些模式或许无法满足需求。考虑到这些模式无处不在,我不想做过多地扩展,只是对第一种模式做一个简单的展示。读完此文,
你可以了解可以用分布式事务做些什么、不能做什么以及如何、何时避免使用XA,何时必须使用。
分布式事务以及原子性
分布式事务涉及不止一个事务资源。比如,在关系数据库和消息中间件之间通信的连接器,通常这些资源拥有类似begin()、rollback()、
commit()的API。在此,一个事务资源通常是一个工厂产品,这个工厂通常由底层平台提供:以数据库为例,DataSource提供
Connection,或者Java Persistence API(JPA)的EntityManager接口;又如Java Message Service(JMS)提供的Session。
一个典型的例子,一个JMS消息触发一次数据库更新。此过程可以分解成一时间线,一个成功的交互顺序是下面这样:
开启消息事务
接受消息
开启数据库事务
更新数据库
提交数据库事务
提交消息事务
如果数据库出错,比如更新时出现诸如违反约束的问题,一个理想的顺序应该是下面这个样子:
开启消息事务
接受消息
开启数据库事务
更新数据库失败
回滚数据库事务
回滚消息事务
在这个案例中,最后的回滚发生后消息返回给中间件,并且在某种程度返回的消息会被其他事务所接收。通常这是件好事,可能你并没有对失败做记录。自动重试处理异常机制超出了本文的范畴。
以上两种时间线中最重要的特性是它们的原子性,形成一个单一的逻辑事务单元,要么都成功要么都失败。
那么用什么确保时间线会的顺序呢?事务资源之间必须保持某种同步,一旦对某个数据源做提交,要么都提交了,要么都回滚。否者整个事务就不缺乏原子
性。之所以是分布式事务,是因为有多个数据源,没有同步就没有原子性。分布式事务技术和概念的核心问题都是围绕资源的同步或者无法同步展开的。
前三种模式的以下讨论都是基于XA协议,考虑到这三种模式分布广泛,本文不会涉及太多的细节,倘若你熟悉XA模式或许愿意直接跳到共享事务资源模式。
二阶段提交完整XA协议
如果你需要近乎完美的防护
(close-to-bulletproof)确保你的应用事务在断电后恢复以及服务器崩溃,完整XA是不二之选。共享资源通常需要做事务同步,在此情况
下,它是一个采用XA协议协调处理过程的信息特殊的事务管理器。在Java领域,从开发者的角度看,这个协议是通过JPA
UserTransaction暴露给大家。
基于系统接口,XA作为一种促成科技(enabling technology)对多数开发人员不可见,因此他们需要知道XA在哪、促成什么、耗损如何以及如何利用事务资源。事务管理器采用二阶段提交(2PC)协议,在确保事务结束前所有资源采用同一个事务结果的同时,也会带来性能耗损。
如
果是Spring促成的(Spring-enabled),应用会采用Spring的JtaTransactionManager以及Spring声明式
事务管理,这样会隐藏到了底层事务同步的具体细节。对于开发人员用没用XA的差别就在于对工厂资源的配置:DataSource实例,以及应用的事务管理
器。本文会通过一个应用案例(atomikos-db项目)来揭示这个配置,数据库实例和事务管理器仅是XA或者JTA特定的应用元素。
为了揭示此案例如何工作,在com.springsource.open.db.下运行这个单元测试。一个简单的 MulipleDataSourceTests类仅是将数据插入两个数据源中,并且采用Spring整合支持的特性对事务进行回滚,代码见清单1:
清单1、事务回滚
@Transactional
@Test
public void testInsertIntoTwoDataSources() throws Exception {
int count = getJdbcTemplate().update(
"INSERT into T_FOOS (id,name,foo_date) values (?,?,null)", 0,
"foo");
assertEquals(1, count);
count = getOtherJdbcTemplate()
.update(
"INSERT into T_AUDITS (id,operation,name,audit_date) values (?,?,?,?)",
0, "INSERT", "foo", new Date());
assertEquals(1, count);
// Changes will roll back after this method exits
}
接着验证这两个操作是否同时回滚,代码清单如清单2:
清单2、回滚验证
@AfterTransaction
public void checkPostConditions() {
int count = getJdbcTemplate().queryForInt("select count(*) from T_FOOS");
// This change was rolled back by the test framework
assertEquals(0, count);
count = getOtherJdbcTemplate().queryForInt("select count(*) from T_AUDITS");
// This rolled back as well because of the XA
assertEquals(0, count);
}
更进一步理解Spring事务管理如何工作以及如何配置,请参看Spring参考指南。
一阶段提交优化XA协议
许多事务管理器采用这种优化模式,可以避免单一事务资源下的2PC过度开销,你的应用服务器最好能够判别此种情况。
协议和最终资源策略
多数XA事务管理器另一个特性是,不论是单一XA兼
容资源还是所有资源都XA兼容,事务管理器均能提供相同的恢复保障。它们是通过给资源排序,并且给非XA资源投票实现,倘若事务提交失败,所有其他的资源
都能回滚。事务有近乎百分百的保障,但缺点是,倘若事务失败,此时不会留下太多信息。换言之,如果要获取这些信息,需要做一些额外的步骤,比如在一些高级
实现。
共享事务资源模式
这个模式不错,系统所有的事务资源由一个相同的资源提供支持进而移除XA,降低系统的复杂度,提高吞吐量。当然不能拿来处理所有的用例,但却是如XA般坚固,而且处理速度更加的快。共享事务资源模式作为一种保障存在与特定的平台和处理场景中。
一个简单熟悉的例子就是共享一个数据库的Connection,它存在于一个对象关系模型(ORM)控件和一个JDBC控件之间。Spring事务管理器就是如此,它支持ORM工具,比如Hibernate、EclipseLink以及Java Persistence API(JPA)。相同的事务能安全的跨越ORM和JDBC控件之间,通常此事务是由service层受事务控制的执行方法所驱动的。
此模式的另外一个特点是,消息驱动的单个数据库更新,如本文初始阶段的简单例子。消
息中间件系统需要存储这些数据,通常是关系型数据库。实现这种模式,需要将消息系统指定到相同的用于存储业务数据的数据库中。这种模式依赖消息中间件供应
商所提供的存储策略细节,以便能够将消息中间件配置在相同的数据库中,并嵌入相同的事务处理。
不是所有的供应商都提供了此种模式,不过一种可替代,几乎可以用于任何数据库的方式,即利用Apache ActiveMQ的传递消息,并且插入一个存储策略进入消息代理中。一旦你知道了其中的诀窍,配置起来很容易的,我会在本文的shared-jms-db 项目案例中演示。此模式的所用的代码无需关注,它们会在Spring配置中得到声明。
名为 SynchronousMessageTriggerAndRollbackTests
的案例中,单元测试校验所有与同步消息接收者相关的讯息。
testReceiveMessageUpdateDatabase方法接受两个消息,接着利用消息插入两条记录到数据库中。如果此方法退出,测试框架回
滚事务,这样就能校验消息和数据库更新是否发生回滚,如清单3所示:
清单3、验证消息和数据库更新是否回滚
@AfterTransaction
public void checkPostConditions() {
assertEquals(0, SimpleJdbcTestUtils.countRowsInTable(jdbcTemplate, "T_FOOS"));
List<String> list = getMessages();
assertEquals(2, list.size());
}
配置文件最重要的部分就是ActiveMQ的持久化策略,连接消息系统和相同数据源作为业务数据,Spring JmsTemplate的flag标签用来接收消息。清单4 展示了如何配置ActiveMQ持久化策略:
清单4、ActiveMQ持久化配置
<bean id="connectionFactory" depends-on="brokerService">
<property name="brokerURL" value="vm://localhost?async=false" />
</bean>
<bean id="brokerService" init-method="start" destroy-method="stop">
<property name="persistenceAdapter">
<bean>
<property name="dataSource">
<bean>
<property name="targetDataSource" ref="dataSource"/>
<property name="jmsTemplate" ref="jmsTemplate"/>
</bean>
</property>
<property name="createTablesOnStartup" value="true" />
</bean>
</property>
</bean>
清单5展示了Spring JmsTemplate中用来接收消息的flag标签:
清单5、设置JmsTemplate事务应用
<bean id="jmsTemplate">
<!-- This is important... -->
<property name="sessionTransacted" value="true" />
</bean>
若 sessionTransacted不为true,JMS session transaction
API就无法被调用,消息接受者将无法回滚。最为重要的是,嵌入的代理包含一个特殊的async=false参数以及DataSource外包类,这样就
可以确保ActiveMQ拥有同Spring一样的事务JDBC Connection。
一个共享数据库源可以由的单个数据源组成,特别是这些数据源拥有同样的RDBMS平台。企业级数 据库供应商均提供同名概念(the
notion of
synonyms)支持,表可以作为一个同名(synonyms)声明于多个schema中。借助这个手段,分布在不同物理平台上的数据,可以均可
JDBC
client同意Connection事务管理。比如在一个真实的系统中,采用ActiveMQ共享资源模式实现,通常需要为消息和业务数据创建同名。