由于业务需要将quartz的jobstore从JobStoreTx更新为JobStoreCMT,接着启动servlet时发现被锁住了。由于使用jobstorecmt我们使用了managed datasource,按照quartz文档的要求我也配置了non managed datasource,在quartz中什么是managed datasource和non managed datasource呢?
Managed datasource: 使用了cmt才有的概念,应该主要是job执行时还有一些其他和job有关的操作quartz会用到的datasource,这个datasource是由容器来管理的,也就是一般意义上我们使用的managed datasource,但是它还强调的是transaction是由容器或者用户自己的程序来控制(JTA)。quartz的job执行提供了一个UserTransaction的wrap。
Non managed datasource; JobStoreTX默认就是使用这个datasource,默认是dbcp的pool,这个non managed datasource不是指我们不能使用容器管理的datasource,也可以配置成容器管理的datasource的,但要注意的是transaction level是local的,transaction是由quartz控制的,quartz来完成transaction的语义和边界。
一开始的配置如下:
# The value of org.quartz.scheduler.instanceName
# can be any string, and has no meaning to the scheduler itself -
# but rather serves as a mechanism for client code to distinguish schedulers
# when multiple instances are used within the same program. If you are using
# the clustering features, you must use the same name for every instance in
# the cluster that is 'logically' the same Scheduler.
#
# NOTE: Especially for the application using LTS, the instanceName is different and
# specific per application to avoid the interruption between applications. For
# example in TMS project the instanceName could be named as TMS_APP, in DMS
# project the instanceName could be named as DMS_APP etc, the instanceName in
# different projects must be different. For more details, you could refer to
# the LTS installation guide.
org.quartz.scheduler.instanceName = LTS
org.quartz.scheduler.rmi.export = true
org.quartz.scheduler.rmi.registryHost = localhost
org.quartz.scheduler.rmi.registryPort = 1100
org.quartz.scheduler.rmi.createRegistry = as_needed
org.quartz.scheduler.wrapJobExecutionInUserTransaction = true
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = 10
org.quartz.threadPool.threadPriority = 5
org.quartz.jobStore.misfireThreshold = 2592000000
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreCMT
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.oracle.OracleDelegate
org.quartz.jobStore.useProperties = false
org.quartz.jobStore.dataSource = myXADS
org.quartz.jobStore.nonManagedTXDataSource = myDS
org.quartz.jobStore.tablePrefix = QRTZ_
org.quartz.jobStore.isClustered = false
org.quartz.jobStore.selectWithLockSQL =
org.quartz.dataSource.myXADS.jndiURL = jdbc/ltstxxatest
org.quartz.dataSource.myDS.driver = oracle.jdbc.OracleDriver
org.quartz.dataSource.myDS.URL = jdbc:oracle:thin:@shhpdv11:1521:WOSDB
org.quartz.dataSource.myDS.user = sysint
org.quartz.dataSource.myDS.password = sysint
org.quartz.dataSource.myDS.maxConnections = 5
datasource的配置:
<managed-data-source name="LTSTxTestXADataSource"
connection-pool-name="LTSTxTestXAConnectionFactory"
jndi-name="jdbc/ltstxxatest" tx-level='global' />
<!-- tx-level='global' -->
<connection-pool name="LTSTxTestXAConnectionFactory">
<connection-factory
factory-class="oracle.jdbc.xa.client.OracleXADataSource"
user="sysint" password="sysint"
url="jdbc:oracle:thin:@shhpdv11:1521:WOSDB">
</connection-factory>
</connection-pool>
但是发现servlet在启动时hold在那里了,跟了一下代码,发现quartz jobstoreCMT默认是使用的oracle db的TM锁,而jobstoreTX使用的是java的逻辑锁,于是登录到db中,查了一下,发现一个insert quartz_simple_triggers的DML操作申请了Qaurtz_Locks的表锁,而接着start quartz scheduler时也会去申请这个表锁,但是前面的DML操作一直都没有commit,所有quartz scheduler start时就被锁在那里了。然后试着将non managed datasource和managed datasource配成同一个datasource启动ok没有问题,但是正是因为配了同一个datasource可能下一次从pool中拿的connection的oracle session还是拥有那个锁资源的,锁资源没有被先前的connection释放掉,因为事务没有提交,connection的逻辑close不会释放session拥有的资源。这样又回到了原点,白试了一把,后来仔细跟了一把程序,发现autocommit是false,也没有地方显著的提交,scheduleJob和unscheduleJob都会有这种情况,本来以为只有job执行的时候会用到managed datasource,这样看来很多对job信息的处理的地方都用到了managed datasource,但是为什么quartz不帮我们wrap起来提交呢,控制transaction的任务交给了我们自己或container,交给我们自己是指我们需要自己用JTA transaction封装这些操作,交给container就需要把属性:
org.quartz.jobStore.dontSetAutoCommitFalse = true
但是quartz文档中提到下面这一段:
org.quartz.jobStore.dontSetAutoCommitFalse
|
False
|
Description: Setting this parameter to true tells Quartz not to call setAutoCommit(false) on connections obtained from the DataSource(s). This can be helpful in a few situations, such as if you have a driver that complains if it is called when it is already off. This property defaults to false because most drivers require that setAutoCommit(false) be called.
|
大概意思是大多数jdbc driver是不用去设置auto commit为true的,保持默认值是false就可以了,又看到了这篇文章:
http://forums.opensymphony.com/thread.jspa?threadID=14762&messageID=28963#28963
主要是说auto commit设成true会改变quartz transaction的语义,本来时多步操作提交的,现在变成了每步dml操作都会提交。但是如果不设auto commit在oracle jdbc中似乎没办法再提交这个transaction了,锁就不会被释放。于是找了找,找到了下面这篇文章:
http://forums.opensymphony.com/thread.jspa?threadID=365430&messageID=452688#452688
这篇文章也遇到了类似这样的问题,提交了trigger但是没有到db中,应该也是被锁住了。提出的解决办法就是把auto commit设成true。接着尝试了一下,顺利成功启动,trigger成功调度,job成功执行。表面上看似没有什么问题,但是一直还是有concern,因为我们这样设置会不会更改了quartz事务的语义,仔细看了quartz jobstore相关的代码,jobstorecmt中其实还有一个属性:
// Great name huh?
protected boolean dontSetNonManagedTXConnectionAutoCommitFalse = false;
文档中的描述:
org.quartz.jobStore.dontSetNonManagedTX ConnectionAutoCommitFalse
|
False
|
Description: This is the same as the property org.quartz.jobStore.dontSetAutoCommitFalse, except that it applies to the nonManagedTXDataSource.
|
默认值是false,所有non managed datasource都是用的这个值,auto commit是false,设置org.quartz.jobStore.dontSetAutoCommitFalse = false不会影响non managed datasource的transaction语义。这样想想把这个值设成true也不会有什么问题,不过进一步还是要进行大量的测试。所以oracle application server使用jobstorecmt后quartz的配置应该如下:
# The value of org.quartz.scheduler.instanceName
# can be any string, and has no meaning to the scheduler itself -
# but rather serves as a mechanism for client code to distinguish schedulers
# when multiple instances are used within the same program. If you are using
# the clustering features, you must use the same name for every instance in
# the cluster that is 'logically' the same Scheduler.
#
# NOTE: Especially for the application using LTS, the instanceName is different and
# specific per application to avoid the interruption between applications. For
# example in TMS project the instanceName could be named as TMS_APP, in DMS
# project the instanceName could be named as DMS_APP etc, the instanceName in
# different projects must be different. For more details, you could refer to
# the LTS installation guide.
org.quartz.scheduler.instanceName = LTS
org.quartz.scheduler.rmi.export = true
org.quartz.scheduler.rmi.registryHost = localhost
org.quartz.scheduler.rmi.registryPort = 1101
org.quartz.scheduler.rmi.createRegistry = as_needed
org.quartz.scheduler.wrapJobExecutionInUserTransaction = true
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = 10
org.quartz.threadPool.threadPriority = 5
org.quartz.jobStore.misfireThreshold = 2592000000
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreCMT
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.oracle.OracleDelegate
org.quartz.jobStore.useProperties = false
org.quartz.jobStore.dataSource = myXADS
org.quartz.jobStore.nonManagedTXDataSource = myDS
org.quartz.jobStore.tablePrefix = QRTZ_
org.quartz.jobStore.isClustered = false
org.quartz.jobStore.dontSetAutoCommitFalse = true
org.quartz.jobStore.selectWithLockSQL =
org.quartz.dataSource.myXADS.jndiURL = jdbc/ltstxxatest
org.quartz.dataSource.myDS.driver = oracle.jdbc.OracleDriver
org.quartz.dataSource.myDS.URL = jdbc:oracle:thin:@shhpdv11:1521:WOSDB
org.quartz.dataSource.myDS.user = sysint
org.quartz.dataSource.myDS.password = sysint
org.quartz.dataSource.myDS.maxConnections = 5