quartz非集群共享数据库的bug——quartz诡异问题解决一例

来源:互联网 发布:淘宝优惠券 编辑:程序博客网 时间:2024/05/29 13:28

     首先,我只是个菜鸟,没什么经验,所写的东西都是自己从工作或学习中总结出来的,所以,文章中难免有谬误,希望大家指正。

     现在的项目中用到quartz做任务调度,由于项目组的同事以前都没用个这个东西,所以安排我研究quartz,然后给各位同事培训,还要在quartz上封装一层更简单易用的接口,项目中quartz相关的问题也由我维护,项目开发过程中quartz表现的一直很乖巧,但是就在前几天出现的一个问题差点儿让我抓狂。

     下面说一下项目中quartz的配置和部署情况:在开发阶段quartz是非集群部署的,jobstore使用JobStoreTX,数据库用的是项目组公用的oracle数据库。quartz的配置文件如下:

 

 

可以看到没有org.quartz.jobStore.isClustered 这个配置,将会采用默认值false,即非集群。

      我在项目中开发的一个模块是任务控制,就是提供图形界面对任务进行增删改查,还可以对任务进行启动、暂定、关闭等控制操作。对已经启动的任务还可以进行跟踪,即可以查看任务的状态,查看任务的执行情况,比如何时执行,执行成功还是失败,失败的信息等,跟踪单元的实现方式是使用job listener将任务的执行信息写入到一个数据库表中,任务跟踪页面从该表中读取某个任务的跟踪信息。这个模块放到基线中。同事们的项目源码都是从同一个基线checkout出来的,在自己机器上进行开发和部署。

第一个问题:

      随后在开发过程中我就遇到了一个诡异问题,我在我本机使用任务控制模块添加并且启动一个测试任务,这个任务很简单,就几行代码:

 

要做的就是当这个任务的执行时间到达时就把本机的当前时间写入的c盘的一个文件中。该任务的时间计划是每分钟的第30秒执行一次。然后我在跟踪页面看这个任务的执行情况,奇怪的是,当我在跟踪页面看到任务成功执行之后,我去c盘找该任务写入的文件,发现里面什么也没有。我去直接数据库中看这个任务的执行情况信息,发现确实写入进去了,说明任务已经执行了,而且job listener也执行了。那为什么c盘的文件里没有东西呢?

第一次尝试解决:

      于是我第一反应就是任务的 job class写的有问题,于是给任务的job class中的execute方法下个断点,看看它到底干了什么。

问题继续:

      结果更诡异的现象发生了:当我看到任务执行成功的跟踪信息之后,我下的断点竟然没停。但是任务的跟踪信息确确实实产生了。难道是job class 的execute方法根本没执行?

第二次尝试:

      这个很诡异,需要分析一下。首先任务的跟踪信息是由job listener在job执行之后写入到数据库中的,那可以断定job listener的jobExecuted方法肯定执行了。于是我在job listener的jobExecuted()方法中下断点。

问题依旧,又有新疑点:

      结果,这个断点在任务执行的时候也没停。而且我在调试过程中发现,在任务的数次执行过程中我的任务偶尔能执行一次,而且能停到断点处,让后我单步跟踪,没有发现问题,然后去c盘找它写入的文件,发现当前时间也写进去了。但这只是偶尔那么一两次。而且,跟踪信息也很奇怪,比如这个任务的执行计划是每分钟的第30秒执行一次,但任务的执行记录中所记下的任务执行时间有很多都不是每分钟的第30秒。

再分析:

      我反复试了好多次,发现一个规律,如果我下的断点可以停,那么c盘下的那个文件就会写入新的当前时间,如果不停那个文件就不会更新。而且,虽然c盘下的那个文件只是偶尔几次更新,但是任务的跟踪信息却每次都产生,虽然执行记录所记下的时间与任务的时间计划有很大误差。那就要分析了,首先,任务的执行记录每次都都会产生(虽然跟任务的时间计划有误差),那就说明程序确实吧任务的执行情况写入到数据库中了。那就说明项目中确实有代码把任务的执行情况写入到数据库的任务跟踪信息表中了。但是我在job listener下的断点为什么大部分情况下都没停呢?难道项目中有其它代码将任务的执行情况更新到数据库中?这个不大可能,因为这一块一直都是我自己再写,别的同事也不了解我的数据库表设计(虽然我们用的是一个数据库实例)。那就说明job listener确实执行了,job listener只有在监听到job class的execute()方法执行之后才会执行,那说明job class 的execute()方法确实执行了,也即任务执行了。但问题是,既然任务执行了,为什么没有更新c盘下的那个文件呢?

绞尽脑汁的分析:

      确实有代码更新数据库了。但是我在job class和job listener 上下的断点确实没停,但是偶尔也会停。会是什么问题呢?绞尽脑汁,绞尽脑汁。看到同事们正在埋头工作,忽然灵光一闪,想到一个大胆的想法,会不会是同事部署的项目执行了这个任务呢?想到这个忽然变的很激动。要是同事们都把她们部署的项目给停了,只有我自己运行会怎样呢?但是十好几个同事总不能让别人停止工作给我搞测试吧,于是我修改了quartz.properties文件,把jobstore改为RAMJobstore,然后提交之后让同事们更新,这样同事们都使用RAMJobstore了。我自己的配置文件还是用JobstoreTX,这样只有我自己在使用数据库的jobstore了。然后我测试,果然不出所料,我添加的任务无一例外都执行了,代码走到断点处也都停了。而且c盘里的那个文件也按时更新,一点儿问题没有。原来真的是别人的机器执行了这个任务啊。问题解决了之后都想跳楼了。

总结:

      这个问题涉及到quartz对job和trigger的存储方式和读取方式。当使用jdbcJobstore时,quartz会把所有的job,trigger和listener存放到数据库中,当quartz启动之后会有线程(QuartzSchedulerThread类的对象)不断扫描数据库,以寻找将要执行的任务(其实是扫描trigger,为了便于与前面所说的统一,暂时说成是扫描作业吧),这个要将要执行的任务是通过本机的当前时间和任务的下次执行时间比较的来的。当任务要执行时,quartz就从数据库中读取job和job的参数信息拿到应用中去执行。这是个十分粗略的描述,实际上这一块儿要更复杂。

      前面说项目组的同事都是从基线checkout出来的源码,大家的源码中都会有任务控制模块有quartz,而且大家的quartz的配置文件一样,jobstore都使用jobstoreTX,虽然quartz配置文件中没有打开quartz 的集群。但是大家都用的同一个数据库实例,所以问题就来了,当项目组的同事们都部署上项目并启动之后,大家的quartz都会去扫描同一个数据库中的任务。但是大家机器时间上的不一致,基于quartz获得将要执行的任务的方式,肯定是时间快的机器首先得到将要执行的任务去执行,于是这个任务就会在这台机器的c盘的文件中写入当前时间,任务执行完之后job listener会将任务的执行情况写入到数据库表中。由于任务在一个机器上已经执行过了,所以其他机器都不会再执行该任务,但是所有部署了这个项目的机器都可以在跟踪页面看到任务的跟踪信息,虽然这个任务并没在他本机执行。

      这样就可以解释为什么我能看到任务的跟踪信息,但是在我的c盘下找不到这个任务写入的文件,而且断点也不停;也可以解释为什么偶尔我的机器会执行这个任务,因为在偶尔一段时间别人都没有启动项目,只有我自己启动了。

 

收藏到抽屉

原创粉丝点击