在 WebSphere JDBC Adapter 中如何避免死锁和锁超时

喝绿茶的猫 2015-05-30

在WebSphereJDBCAdapter中如何避免死锁和锁超时

转自http://www.ibm.com/developerworks/cn/websphere/library/techarticles/1005_yuanjs_jdbcadapter_lock/

引言

WebSphereJDBCAdapter是一个提供J2EE应用和数据库供应商的EIS解决方案之间的连接的资源适配器,是构建在WebSphereProcessServer之上的WebSphereAdapter。不用应用之间的数据交换发生在数据库层次,Adapter使用SQL语句或存储过程以业务对象(BusinessObject,BO)的形式来传递数据,实现数据库与其他应用系统的集成。

WebSphereJDBCAdaper可以集成任意构建在使用JDBC驱动(JDBC2.0以上版本)的数据库上的企业应用,基于J2EEJCA技术实现了JDBCAPI,提供了Inbound和Outbound两种操作来连接数据库。其中,在Outbound操作下,BO从应用系统传递到数据库中,根据BO中指定的动作(Create,Update,Delete,Retrieve,,RetrieveAll,Execute,Exists),BO作为一个请求发送到Adapter中,Adapter将BO传递到具体数据库应用的表中。如果有需要,还可以进一步传递到其他构建在这个数据库之上的应用系统中,并进行相应处理。在outbound操作中,将会涉及到数据库的事务操作处理,继而会产生数据库的死锁和锁超时问题。接下来,看看死锁和锁超时分别在怎样的场景下会产生。

并发调用WebSphereJDBCAdapter的场景导致死锁现象

当WebSphereJDBCAdapter在outbound组件被并发调用的情况下,很可能产生死锁。在图1中,我们可以看到outbound组件的create,delete操作被并发的调用很多次。更为普遍的是,当多次发起用户请求的时候,会导致多线程场景。例如,outbound组件在一个工作流中被使用,用户的多请求导致多个工作流并发的运行起来,也导致outbound组件也处在一个并发调用的情况下。

图1.BPEL中的并发情况

图1:BPEL中的并发情况

当outbound组件在并发调用比较高的情况下,会抛出下面死锁的异常信息:

清单1.死锁异常信息

[12/30/0913:23:22:078CST]00000055FFDC

Zcom.ibm.ws.sca.internal.j2c.J2CMethodBindingImpl

com.ibm.ws.sca.internal.j2c.J2CMethodBindingImpl#0x04Exception:

javax.resource.ResourceException:javax.resource.ResourceException:

DB2SQLError:SQLCODE=-911,SQLSTATE=40001,

SQLERRMC=2,DRIVER=3.50.152,errorcode:-911

atcom.ibm.j2ca.jdbc.commands.JDBCDeleteCommand.execute(JDBCDeleteCommand.java:379)

atcom.ibm.j2ca.extension.commandpattern.CommandForCursor.execute

(CommandForCursor.java:68)

atcom.ibm.j2ca.extension.commandpattern.Interpreter.

executeWithChildren(Interpreter.java:96)

atcom.ibm.j2ca.extension.commandpattern.Interpreter.execute(Interpreter.java:77)

atcom.ibm.j2ca.jdbc.JDBCInteraction.executeInternal(JDBCInteraction.java:358)

atcom.ibm.j2ca.jdbc.JDBCInteraction.execute(JDBCInteraction.java:139)

atcom.ibm.ws.sca.internal.j2c.J2CMethodBindingImpl.invoke(

J2CMethodBindingImpl.java:242)

在特定场景下产生锁超时

在图2的场景之下,WebSphereJDBCAdapter产生的两个outbound组件加入到两个不同的BPEL组件中。在这两个BPEL组件中,“BPELGlobalTransaction”组件使用了全局事务,另外一个“BPELLocalTransaction”组件使用了本地事务。在WebSphereJDBCAdapter产生的这两个outbound组件中,使用全局事务的“globaltransaction”outbound组件完成删除主键为“1”的记录的行为,另外一个使用本地事务的“localtransaction”outbound组件则要完成创建一个主键为“1”的记录的行为。在这个场景中,使用了DB2数据库,其数据库参数死锁检查时间和锁超时检查时间分别是“10”秒和“120”秒:

Intervalforcheckingdeadlock(ms)(DLCHKTIME)=10000

Locktimeout(sec)(LOCKTIMEOUT)=120

图2.在事务中运行的WebSphereJDBCAdapter的outbound组件

图2在事务中运行的WebSphereJDBCAdapter的outbound组件

“BPELGlobalTransaction”组件的内部业务逻辑比较复杂,在实际运行中要5分钟左右时间才能够完成。但是“BPELLocalTransaction”组件却比较简单,只需要几秒的时间就可以结束。首先启动“BPELGlobalTransaction”组件的运行,然后再启动“BPELLocalTransaction”组件的运行,大概2分多钟之后,WebSphereProcessServer报告如下锁超时的异常:

清单1.锁超时异常信息

[12/30/0911:38:29:062CST]00000072FFDCZ

com.ibm.ws.sca.internal.j2c.J2CMethodBindingImpl

com.ibm.ws.sca.internal.j2c.J2CMethodBindingImpl#0x04Exception:

javax.resource.ResourceException:

DB2SQLError:SQLCODE=-911,SQLSTATE=40001,SQLERRMC=68,DRIVER=3.50.152,

errorcode:-911

atcom.ibm.j2ca.jdbc.JDBCDBOperationHandler.executePreparedCUDStatement

(JDBCDBOperationHandler.java:216)

atcom.ibm.j2ca.jdbc.JDBCDBOperationHandler.executeSQL(

JDBCDBOperationHandler.java:646)

atcom.ibm.j2ca.jdbc.commands.JDBCCreateCommand.execute(

JDBCCreateCommand.java:309)

atcom.ibm.j2ca.extension.commandpattern.CommandForCursor.execute

(CommandForCursor.java:68)

atcom.ibm.j2ca.extension.commandpattern.Interpreter.executeWithChildren

(Interpreter.java:96)

atcom.ibm.j2ca.extension.commandpattern.Interpreter.execute(

Interpreter.java:77)

atcom.ibm.j2ca.jdbc.JDBCInteraction.executeInternal(

JDBCInteraction.java:358)

atcom.ibm.j2ca.jdbc.JDBCInteraction.execute(JDBCInteraction.java:139)

atcom.ibm.ws.sca.internal.j2c.J2CMethodBindingImpl.invoke(

J2CMethodBindingImpl.java:242)

WebSphereProcessServer中SCA组件事务介绍

因为WebSphereJDBCAdapter运行在WebSphereProcessServer(WPS)之上,其中对数据库事务的控制和管理则是由WPS负责完成的,所以必须先了解相关的WPS的组件事务知识。

WebSphereProcessServer(WPS)是建立在WebSphere应用服务器上的新一代业务集成平台。它支持面向服务的应用架构和企业服务总线,含有符合业界标准的业务流程引擎,使用统一的服务调用和业务表现模型,并遵循业界的开放标准。服务组件架构(ServiceComponentArchitecture,SCA)是WPS新的编程模型,为其提供了统一的服务调用模型,也提供完整的事务支持。图3是SCA的组件模型,一个SCA组件是由接口、服务实现和引用三个部分构成:

图3.SCA组件模型

图3:SCA组件模型

其中接口部分表示该组件能提供的业务能力;实现则是实现了该接口的组件;而引用则表示在服务实现中需要调用到的其他服务。在事务方面,SCA通过在三个部分(接口、服务实现和引用)分别提供不同的限定符(qualifier)的方式来完成事务的定制。

图4.接口上的事务限定符

图4:接口上的事务限定符

图4显示了接口部分的限定符有:JoinTransaction。可以取值true或者false。这个限定符表示该组件是否愿意加入到调用方的事务中。

图5.实现上的事务限定符

图5:实现上的事务限定符

图5显示了实现部分的限定符有:Transaction。可以取值Global、Local或者Any。这个限定符表示该组件对运行环境的需求。global表示需要运行在一个全局事务中,这样SCA容器必须保证在调用该服务实现时总是有一个活动的全局事务。local则表示要运行在本地事务中。容器必须挂起当前活动的全局事务,并负责启动一个本地事务。any则表示该实现既可以运行在一个已有的全局事务中也可以运行在本地事务中。如果选择了any,容器如果发现当前没有活动的事务,就会建立一个本地事务。

图6.引用上的事务限定符

图6:引用上的事务限定符

图6显示了引用部分的限定符有:Suspendtransaction。取值为true或者false。用来表示在调用这个目标服务时是否需要把当前的全局事务挂起(但目标服务是否能加入到这个事务中,还取决于目标服务的事务配置)。如果选择true,则表示当前服务的事务不会被传播给所调用的服务。这个限定符只针对同步调用有效。

通过这三个部分的组合就可以实现细粒度的事务控制,这样也就能对各个组件实现更为精确的事务控制。下面的表格1列举出Jointransaction和transaction两种选项组合的情况下,如何实现对事务的管理和实现。

表1.目标组件的事务行为

接口

“JoinTransaction”qualifier

实现

“Transaction”qualifier

目标组件的事务行为

TrueGlobal如果目标组件已有全局事务的上下文环境,那么目标组件就参与到已有全局事务中去;否则,运行时产生一个新的全局事务。

TrueLocal运行时产生一个错误,这个限定符的设置不对。

TrueAny如果目标组件已有全局事务的上下文环境,那么目标组件就参与到已有全局事务中去;否则,运行时产生一个新的本地事务。

FalseGlobal目标组件运行在全局事务中,总是产生一个新的全局事务。

FalseLocal目标组件运行在本地事务中。

FalseAny目标组件运行在本地事务中。

下面给一个具体例子,比如一个SCA组件有如下的事务需求:在运行时候需要有全局事务但不能加入到已有的全局事务中,该组件需要调用到其他的服务,希望其他的服务也能加入到自己的事务中。那么应该如下设计:

•接口的事务限定符应该设置为jointransaction=false,这样就防止了该组件加入到上游的事务中;

•实现上的事务限定符应该设置为transaction=global。这样SCA容器在调用该实现之前就会启动一个全局事务;

•引用上的事务限定符应该设置为suspendtransaction=false。这样组件实现的事务就被允许传播到被调用的服务;

WebSphereJDBCAdapter中outbound组件事务介绍

WebSphereJDBCAdapter的outbound组件只能在接口上进行事务限定符的配置,不可以在实现和引用上进行事务限定符的设置。

图7.WebSphereJDBCAdapter的outbound组件上的事务限定符

图7:WebSphereJDBCAdapter的outbound组件上的事务限定符

在图7中,WebSphereJDBCAdapter只有接口上的事务限定符的设置,没有提供实现和引用上的事务限定符的设置。从这点上来看,它既符合了SCA组件的事务限定符的规则,同时又有它自己的特殊性。

此外,WebSphereJDBCAdapter提供了三种连接方式,分别是“databaseURLmode”,“datasourceJNDImode”,“XAdatasourcenamemode”。它们和WebSphereJDBCAdapter的outbound组件的事务有着很紧密的联系。图8中显示了上面三种连接方式。

图8.WebSphereJDBCAdapter的连接方式

图8:WebSphereJDBCAdapter的连接方式

WebSphereJDBCAdapter根据接口上的事务限定符和三种连接方式确定outbound组件的事务行为,表格2中描述了各种组合之下产生的事务行为。

表2:WebSphereJDBCAdapter的outbound组件的事务行为

接口

“JoinTransaction”qualifier

Outbound组件采用的连接方式

outbound组件的事务行为

True1.使用“datasourceJNDImode”连接方式,且datasource是非XA。

2.或者使用“databaseURLmode”的连接方式。

运行时产生一个错误,这个限定符的设置不对。

False1.使用“datasourceJNDImode”连接方式,且datasource是非XA。

2.或者使用“databaseURLmode”的连接方式。

outbound组件运行在本地事务中。

True1.使用“datasourceJNDImode”连接方式,且datasource是XA。

2.或者使用“XAdatasourcenamemode”连接方式(仅适用于DB2,Oracle数据库)。

如果outbound组件已有全局事务的上下文环境,那么outbound组件就参与到已有全局事务中去;否则,运行时产生一个新的全局事务。

False1.使用“datasourceJNDImode”连接方式,且datasource是XA。

2.或者使用“XAdatasourcenamemode”连接方式(仅适用于DB2,Oracle数据库)

outbound组件运行在本地事务中。

表格2中,当接口上的事务限定符是“jointransaction=true”,同时采用“datasourceJNDImode”连接方式,且WPS上的datasource是XA类型的时候,它会在全局事务下运行;当接口上的事务限定符是“jointransaction=false”,同时采用“datasourceJNDImode”连接方式,且WPS的datasource是XA类型的时候,它则会在本地事务下运行。

在了解了WPS的组件事务和WebSphereJDBCAdapter的outbound组件知识之后,接下来进一步分析死锁和锁超时是如何产生的。

死锁是如何产生的

WebSphereJDBCAdapter在并发情况下产生数据库死锁现象,仅仅出现在它采用“datasourceJNDImode”连接方式的时候。在“datasourceJNDImode”连接方式下,WebSphereJDBCAdapter使用了WPS的DataSource。这个DataSource连接数据库的时候,事务隔离级别是被WPS的DataSource控制的,WebSphereJDBCAdapter并没有提供对事务隔离级别的控制。在缺省情况下,连接DB2和MSSQLServer的时候,WPS上的DataSource事务隔离级别在被设置在“TRANSACTION_REPEATABLE_READ”下;在连接Oracle的时候,WPS上的DataSource事务隔离级别在被设置在“TRANSACTION_READ_COMMITTED”下。同时,DB2、MSSQLServer、Oracle数据库端的缺省事务隔离级别都是“TRANSACTION_READ_COMMITTED”。WPS上的DataSource事务隔离级别优先级别高于数据库端的隔离级别,所以当使用WebSphereJDBCAdapter的“datasourceJNDImode”连接方式去访问DB2和MSSQLServer数据库的时候,所产生的数据库连接总是在“TRANSACTION_REPEATABLE_READ”事务隔离级别下,它对事务的一致性和完整性要求比较高,会有死锁现象的发生;但是,访问Oracle数据库的时候,所产生的数据库连接则是在“TRANSACTION_READ_COMMITTED”事务隔离级别下,不要求高的事务一致性和完整性,不会有死锁现象的发生。总之,在WebSphereJDBCAdapter被并发调用和在较高的事务隔离级别使用的情况下,会对数据库中被访问的表和行产生很多锁,以及出现锁升级的情况,从而导致死锁现象的发生。如果事务隔离级别设置的越高,数据完整性就越高,对并发支持和性能就会降低;如果事务隔离级别设置的越低,对并发支持和性能提高越好,但是数据完整性就会降低。

如何解决死锁问题

要解决死锁问题,可以通过更改数据库连接的事务隔离级别来达到消除死锁现象的发生,具体的更改措施有如下几种。

•更改WPS上DataSource的缺省事务隔离级别,将缺省的“TRANSACTION_REPEATABLE_READ”事务隔离级别降低到“TRANSACTION_READ_COMMITTED”,详情请查阅“参考资料”一节。

•更改SCAcomponent上EJB的事务隔离级别,详情请查阅“参考资料”一节。

当WPS上的DataSource事务隔离级别降低之后,应用程序对并发的支持和性能提高了,从而也就避免数据库死锁现象的发生。

锁超时是怎么产生的

在图2场景中,DB2数据库端的死锁检查时间是10秒,锁超时检查时间是120秒,也就是说,在数据库端,每过10秒中要检查数据库中是否存在死锁现象,每过2分钟要检查是否有锁超时现象。在“globaltransaction”outbound组件中,当它要执行删除主键为“1”的记录的时候,因为同时它已加入到了“BPELGlobalTransaction”组件发起的全局事务中去,所以它必须等待这个BPEL组件所有业务逻辑操作全部都执行完毕,这个删除的操作才能最终提交到数据库中去。在没有执行完BPEL组件中所有的业务逻辑前,这个删除操作一直不能提交到数据库。这会导致主键为“1”的记录在数据库上被锁住不放。在这个场景中,由于这个“BPELGlobalTransaction”组件执行完成需要5分钟的时间,所以数据库会将这条主键为“1”的记录锁住5分钟不放直至业务逻辑完成为止。当这条主键为“1”的记录被锁住的同时,第二个“BPELLocalTransaction”组件也启动了,它需要调用“localtransaction”outbound组件执行创建主键为“1”的记录,此时数据库会检测到这个记录正在被另外一个应用锁住了,所以“BPELLocalTransaction”组件不得不等待在这条记录上锁的释放,过了2分钟以后,数据库检查到这个锁仍然没有释放,但是锁超时的时间已经到达,所以WPS报告锁超时异常,终止“localtransaction”组件创建记录的操作。通过以上分析,如果WebSphereJDBCAdapter产生的outbound组件加入到一个全局事务中后,全局事务的时间过长会产生锁超时的异常。

如何解决锁超时问题

在上节的锁超时问题分析中,因为WebSphereJDBCAdapter的一个outbound组件加入到上游组件的全局事务中去,且很可能这个上游组件的全局事务执行完成所需要的时间超过了数据库锁超时的时间,在这种情况之下,又出现了其他的WebSphereJDBCAdapter的outbound组件也要访问这个全局事务中已经访问过的记录,此刻就会出现锁超时现象。为了避免这种现象,从设计上就要考虑,应该尽可能的使得全局事务消耗的时间要短,在不同应用中要避免访问相同的表或者记录。当然,适当调整数据库锁超时时间也是可行方法之一。

总结

本文首先引入在WebSphereJDBCAdapter上产生死锁和锁超时的场景,然后介绍了WPS中SCA组件的事务特性,接着论述了WebSphereJDBCAdapter的outbound组件事务的特别之处,然后在实际应用中,针对事务引发的死锁和锁超时的现象寻找原因和进行分析,最后提供了解决办法去避免死锁和锁超时现象。

相关推荐