Spring对Quartz的封装实现简单分析及使用注意事项

来源:互联网 发布:acrobat xi for mac 编辑:程序博客网 时间:2024/05/28 14:56
前段时间在项目中一直使用正常的Quartz突然出现了任务漏跑的情况,由于我以前看过Quartz的内部实现,凭借记忆我觉得是由于Quartz的线程池的使用出现问题导致了故障的发生。为了搞清问题的真相,我又重新看了一下Quartz的代码,以下是我对Quartz的一些分析,欢迎各位大牛拍砖。 

Spring对Quartz的封装实现简单分析及使用注意事项


在看Spring的代码时发现Spring对Quartz封装过以后对Quartz的初始化过程还是比较复杂的,我对比较关键的几点提取出来画出了上面的时序图。大家可以结合代码看上面的时序图应该可以理解Quartz的初始化过程。图中的SpringContext只是用来代表Spring容器,我们在使用Quartz时没有对它进行特殊的配置,因此它各种参数都是默认的。特别是“org.quartz.threadPool.threadCount”这个参数Scheduler中线程池的大小也是使用了Spring的默认值10,这个默认值就有可能是造成我们的系统线上定时任务故障的原因,下面我再详细的解析一下。 

Spring对Quartz的封装实现简单分析及使用注意事项


QuartzScheduler是整个定时任务框架工作的核心类,上面的类图仅仅展现了QuartzScheduler中几个核心成员。QuartzSchedulerResources可以认为是存放一切配置以及通过配置初始化出来的一些资源的容器,其中包括了存储job定义的jobStore,JobStore可以有多种实现,我们使用的是默认的RAMJobStore;还有一个非常重要的对象就是ThreadPool,这个线程池管理着执行我们定义的Job所需的所有线程。这个线程池的大小配置就是通过我上面提到过的“org.quartz.threadPool.threadCount”进行配置的。QuartzScheduler另一个重要成员就是QuartzSchedulerThread,没有这个线程的话我们所有定义的任务都不会被触发执行,也就是说它是Quartz后台的“守护线程”,它不断的去查找合适的job并触发这些Job执行。下图展现了QuartzSchedulerThread的主要业务逻辑。 

Spring对Quartz的封装实现简单分析及使用注意事项


上图中有一处肯定引起了大家的注意,那就是判断可用线程数的阻塞方法。这个方法当线程池中可用的连接数小于1的时候会调用Object.wait()方法,让QuartzSchedulerThread线程等待直到线程池中有可用的线程以后才返回结果。这样的话如果遇到在某一时刻并发的任务比较多的情况下就极有可能导致有些任务没有按我们预先设定的时间进行执行。 下面再来看一下我对Quartz进行的一个测试: 
1.在Spring配置文件中配置了13个job,每个Job实现代码都一样具体如下: 
public class TestJob1 {protected int times=0;public void doJob2(){times++;System.out.println(this.getClass().getName()+" start job. times:"+times+" "+new Date());try {Thread.sleep(1000L*60L*3L);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}System.out.println(this.getClass().getName()+" end job. times:"+times);}}

2.Spring配置中cronExpression如下: 
<bean id="testJob1" class="com.xxx.xxxx.test.order.TestJob1"/><bean id="testJob1Trigger" class="org.springframework.scheduling.quartz.CronTriggerBean"><property name="jobDetail"><bean class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"><property name="concurrent" value="false"/><property name="targetObject" ref="testJob1"/><property name="targetMethod" value="doJob2"/></bean></property><property name="cronExpression" value="0 0/1 * * * ? *"/></bean><bean id="testJob2" class="com.xxx.xxxx.test.order.TestJob2"/><bean id="testJob2Trigger" class="org.springframework.scheduling.quartz.CronTriggerBean"><property name="jobDetail"><bean class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"><property name="concurrent" value="false"/><property name="targetObject" ref="testJob2"/><property name="targetMethod" value="doJob2"/></bean></property><property name="cronExpression" value="0 0/1 * * * ? *"/></bean><!--这里省略job3-job11的配置,这些JOB的配置同job1,job2--><bean id="testJob12" class="com.xxx.xxxx.test.order.TestJob12"/><bean id="testJob12Trigger" class="org.springframework.scheduling.quartz.CronTriggerBean"><property name="jobDetail"><bean class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"><property name="concurrent" value="false"/><property name="targetObject" ref="testJob12"/><property name="targetMethod" value="doJob2"/></bean></property><property name="cronExpression" value="30 0/1 * * * ? *"/></bean>   <bean id="testJob13" class="com.xxx.xxxx.test.order.TestJob13"/><bean id="testJob13Trigger" class="org.springframework.scheduling.quartz.CronTriggerBean"><property name="jobDetail"><bean class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"><property name="concurrent" value="false"/><property name="targetObject" ref="testJob13"/><property name="targetMethod" value="doJob2"/></bean></property><property name="cronExpression" value="50 0/1 * * * ? *"/>

也就是说除了testJob12和testJob13是我们期望分别在每分钟的30秒、50秒被触发,其他的job都是每分钟的0秒时被触发 
3.下面我们看一下Quartz的执行结果 
com.xxx.xxxx.test.order.TestJob13 start job. times:1 Wed Jun 02 09:12:51 CST 2010 
com.xxx.xxxx.test.order.TestJob10 start job. times:1 Wed Jun 02 09:13:00 CST 2010 
com.xxx.xxxx.test.order.TestJob11 start job. times:1 Wed Jun 02 09:13:00 CST 2010 
com.xxx.xxxx.test.order.TestJob1 start job. times:1 Wed Jun 02 09:13:00 CST 2010 
com.xxx.xxxx.test.order.TestJob2 start job. times:1 Wed Jun 02 09:13:00 CST 2010 
com.xxx.xxxx.test.order.TestJob3 start job. times:1 Wed Jun 02 09:13:00 CST 2010 
com.xxx.xxxx.test.order.TestJob4 start job. times:1 Wed Jun 02 09:13:00 CST 2010 
com.xxx.xxxx.test.order.TestJob5 start job. times:1 Wed Jun 02 09:13:00 CST 2010 
com.xxx.xxxx.test.order.TestJob6 start job. times:1 Wed Jun 02 09:13:00 CST 2010 
com.xxx.xxxx.test.order.TestJob7 start job. times:1 Wed Jun 02 09:13:00 CST 2010 

com.xxx.xxxx.test.order.TestJob13 end job. times:1 
com.xxx.xxxx.test.order.TestJob12 start job. times:1 Wed Jun 02 09:15:51 CST 2010 
com.xxx.xxxx.test.order.TestJob10 end job. times:1 
com.xxx.xxxx.test.order.TestJob13 start job. times:2 Wed Jun 02 09:16:00 CST 2010 

com.xxx.xxxx.test.order.TestJob11 end job. times:1 
com.xxx.xxxx.test.order.TestJob1 end job. times:1 
com.xxx.xxxx.test.order.TestJob3 end job. times:1 
com.xxx.xxxx.test.order.TestJob2 end job. times:1 
…… 我们可以看到在红色区块里是Quartz第一次调度启动的所有JOB,我们可以看到这个时候只启动了10个JOB,job8,9,12因为线程池无可用线程没有被触发起来。 
我们再看绿色区块由于job13第一次的任务执行结束使得线程池中有了空闲的线程,job12得以被触发但是这里请大家注意一下JOB12的开始时间09:15:51这已经不是我们说期望的每分钟的30秒那一刻开始执行。绿色区块的后面两条日志我们也可以看到由于Job10执行完成JOB13开始了第二次执行09:16:00但是Job13的触发时间也不是我们所期望的每分钟的50秒那一刻开始执行。 
后面的日志我就不再解释从上面的日志我们基本上可以看出来,当Quartz得线程池在某一个时刻被占满的时候,后续的一些job无法保证在我们所期望的时间点被执行. 
   通过这个测试得出了我们使用Quartz时需要注意的几点事项: 
1. 同一个时刻,或者相隔较近的一段时间内不能配置超过Quartz线程池大小的任务数。有时候即使是在某时刻任务数配置的不多但是也要关心一下在它的前面是否有大量的耗时的任务,这样同样会有可能导致你的任务不在你期望的时间点被执行。 
2. 在你的定时任务里不要依赖定时钟的启动时间来做一些操作,比如根据当前时间取一些数据的操作,如果有类似这样的操作当定时钟没有在你期望的时间点被触发的时候极有可能造成数据遗漏之类的问题。
0 0
原创粉丝点击