从一个错误谈谈事务管理

来源:互联网 发布:淘宝省钱app有哪些 编辑:程序博客网 时间:2024/06/05 07:32

最近在产品中发现了一个错误,和分布式事务相关,仔细梳理了一下,还发现不少值得深究的东西,下面就分享给大家。

错误堆栈信息比较长,只摘出关键的一段:

[2/28/12 0:57:28:195 MST] 00000016 SystemErr     R Caused by: javax.resource.ResourceException: enlist: caught Exception[2/28/12 0:57:28:195 MST] 00000016 SystemErr     R at com.ibm.ejs.j2c.LocalTransactionWrapper.enlist(LocalTransactionWrapper.java:696)[2/28/12 0:57:28:195 MST] 00000016 SystemErr     R at com.ibm.ejs.j2c.ConnectionManager.lazyEnlist(ConnectionManager.java:2186)[2/28/12 0:57:28:195 MST] 00000016 SystemErr     R at com.ibm.ws.rsadapter.spi.WSRdbManagedConnectionImpl.lazyEnlist(WSRdbManagedConnectionImpl.java:2569)[2/28/12 0:57:28:195 MST] 00000016 SystemErr     R at com.ibm.ws.rsadapter.jdbc.WSJdbcConnection.beginTransactionIfNecessary(WSJdbcConnection.java:714)[2/28/12 0:57:28:195 MST] 00000016 SystemErr     R ... 127 more[2/28/12 0:57:28:195 MST] 00000016 SystemErr     R Caused by: com.ibm.ws.Transaction.IllegalResourceIn2PCTransactionException: Illegal attempt to enlist multiple 1PC XAResources[2/28/12 0:57:28:195 MST] 00000016 SystemErr     R at com.ibm.ws.tx.jta.RegisteredResources.enlistResource(RegisteredResources.java:864)[2/28/12 0:57:28:195 MST] 00000016 SystemErr     R at com.ibm.ws.tx.jta.TransactionImpl.enlistResource(TransactionImpl.java:1781)[2/28/12 0:57:28:195 MST] 00000016 SystemErr     R at com.ibm.ws.tx.jta.TranManagerSet.enlistOnePhase(TranManagerSet.java:612)[2/28/12 0:57:28:195 MST] 00000016 SystemErr     R at com.ibm.ejs.j2c.LocalTransactionWrapper.enlist(LocalTransactionWrapper.java:593)[2/28/12 0:57:28:195 MST] 00000016 SystemErr     R ... 130 more
从错误信息可以看到Illegal attempt to enlist multiple 1PC XAResources,错误的原因似乎和分布式事务相关,那么一个自然而然的想法就是把non-xa的数据源改为XA数据源,问题不就解决了。 无非就是牺牲一点性能,但能保证程序正常工作, 然而事实是如此吗?通过试验我们发现,把这个数据源改为XA后,运行过程中在执行DDL的时候会出错。看来此路不通。什么原因了?原来对于某个RM,如果它执行了一个不可回滚的操作,那么它是不能参与到两阶段事务提交的, 因为两阶段提交的第二阶段是有可能回滚的, 如此一来,就有冲突了。顺便说一下, 这也是为什么下面这些操作是不能出现在分布式事务中的,

执行DDL语句,例如create table; 执行savepoint; 使用db link操作 等等,总而言之,这些操作有个共同点就是他们是不能回滚的,或者是隐式提交。


接下来地思路就是,如何在分布式事务中使用non-xa 的数据源, 会有哪些限制。 在这里,需要提到应用服务器,我们以websphere和weblogic为例, 由于普通的数据源是不支持两阶段提交的,但是在实际中,经常会碰到这种情况,就是已有的操作使用Non-XA数据源,这个操作需要成为分布式事务的一部分,weblogic 提供了模拟两阶段提交,websphere提供了last participant in 作为解决方案。

 Select this option if you want to enable non-XA JDBC connections from the data source to emulate participation in global transactions using JTA. Select this option only if your application can tolerate heuristic conditions.(使用此选项,会将使用连接的事务分支作为事务中最后的资源进行处理,并将其作为本地事务进行处理。两阶段提交(two-phase commit,简称 2PC)的提交记录会插入资源自身的表中,且此结果确定了全局事务准备阶段的成功或失败。与“仿真两阶段提交”相比,此选项具有一些性能优势和更高的数据安全性,但它具有某些限制。请参阅了解“记录上一个资源”选项。)
 Select this option if you want to enable non-XA JDBC connections from the data source to participate in global transactions using the one-phase commit transaction processing. With this option, no other resources can participate in the global transaction.(使用此选项,使用连接的事务分支始终返回事务准备阶段的成功信息。此选项提供了性能优势,但在某些失败情况下也存在破坏数据的风险。只有在应用程序可允许出现启发性错误情况时才能选择此选项。)WebLogic 9中的LLR特性不应该与“Emulate Two-Phase Commit(模拟两阶段提交)”相混淆,后者是以前版本的JDBC连接池层的选项。两者之间有着微妙而重要的区别。根据BEA WebLogic 8的说明文档:

当选中Emulate Two-Phase Commit for non-XADriver选项(EnableTwoPhaseCommit设为true)时,非XA的JDBC资源通常在调用XAResource.prepare()方法时返回XA_OK。该资源试图提交或回滚它的本地事务以响应随后的XAResource.commit()或XAResource.rollback() 调用。如果该资源提交或回滚失败,就会产生一个启发式错误。其结果是应用程序的数据可能会不一致。

而对于LLR,它是针对应用服务器或者说事物管理器上的操作,在具有 LLR 参与者的全局两阶段提交 (2PC) 事务中,WebLogic Server 事务管理器执行下列基本步骤:

  • 在所有其他(符合 XA 的)事务参与者上调用准备。
  • 将提交记录插入 LLR 参与者的表中(而不是基于文件的事务日志中)。
  • 提交 LLR 参与者的本地事务(包括事务提交记录插入和应用程序的 SQL 工作)。
  • 在所有其他事务参与者上调用提交。
  • 在事务成功完成后,从容地将数据库事务日志条目删除(作为未来事务的一部分)。
对于具体细节,可以参看webloic的文档, 包括 LLR 数据源的编程注意事项和限制 以及LLR 数据源的管理注意事项和限制,了解“仿真两阶段提交”事务选项 等。


回到我们这个例子中,使用模拟两阶段提交,能够在weblogic上顺利通过,但是在websphere服务器上,则会出现问题,让我们在看看last participant in 选项。在Webshpere服务器上,对于本地事务,如果他要成为全局事务的一部分,那么要么这是一些LPS只读事务,要么只能有一个LPS的事务。在这里last participant in选项名称有一些误导,他并不是要求这个数据源上的操作必须是事务的最后一步,实际上,事务的提交对于我们来说是不可见的,是由应用服务器(事物管理器)来控制的,在这里我来尝试调整次序,把non-xa数据源放到最后一个,并不奏效。

然而在整个程序中,我们仅仅配置一个non-xa的数据源,无论如何不会这样的问题了,因为LPS是可以有一个non-XA的数据源参与全局事务的。最后通过分析,终于发现了问题。在获取数据源时,我们用了JNDI,然后有的时候用的是绝对路径,如java:comp/ 有的时候直接用相对路径名,例如 user.databasepool, 在应用服务器中,虽然他们实际是指向同一个数据源,然后由于引用的路径地不用,造成WS认为是两个non-XA数据源参与其中,至此,解决的办法也很简单,采用同一种路径去获取数据源,问题解决!

通过这个例子可以看出,在决定数据源及其事务时,有下面这些注意的,

本地事务会优于分布式事务,无论是在性能还是可靠性上,所以我觉得应该优先使用本地事务,例如SAP好像就提倡使用一个数据库,本地事务,分布式事务从概念上来说很好,但是实际实现还是有很多问题的。

如果在某些情况下,必须使用分布式事务,例如多个数据库,JMS等,要考虑全局事务尽量使用XA驱动,对于LLR和EPC,以及LPS,虽然他们可用,但并不安全,相对来说LLR>EPC。

如果使用LPS LLR等还是有冲突,例如两个non-XA数据源要参与全局事物,这时,很可能需要把你的事物分拆成单个的小事务,避免这种大事务包含多个non-xa的情况。