Quartz定时任务调度器堵塞原理和解决

Quartz 调度器以多线程的方式执行调度任务JobDetail,缺省线程池大小为10,也就是说若调度器中已有10个Job在工作(线程没有结束),那么即使有JobDetail到了被触发的时间,新的JobDetail不会被执行,也就是说阻塞的条件是,调度器中正在运行的JobDetail数量达到了设定值10。

举一个具体的例子:

a. 单一Job

配置:

JobA 触发时间为 每秒运行一次,每个Job执行时间为30秒

运行:

1、 10个JobA将连续启动

2、 到第10个JobA启动后,线程池中所有线程被耗尽,调度器出现了阻塞,即没有新的JobA启动,尽管设置为每秒执行一次。

3、30秒后,将有1个以上JobA执行完毕,在短时间内,新的10个JobA又被启动,再次进入2的阻塞状态

2状态可以称做调度器阻塞状态,没有新的Job能执行,导致一些诸如定时读取数据的操作无法继续下去。除非有JobA执行完毕,新的JobA才能被执行。实际运行中,假设调度器中有一个JobA线程的执行时间大于两次启动间隔,则经过若干次操作后,将耗尽所有10个线程资源,导致其他的调度任务阻塞。

b. 多个Job(无状态Job)

在这个测试中,可以有多种不同的Job(无状态Job),但它们均共享这10个线程,任何一个Job 线程执行时间大于两次启动间隔均有可能导致调度器被阻塞。例如:

配置:

JobA 触发时间为 每秒运行一次,每个Job执行时间为30秒;JobB 触发时间为每秒运行一次,每次执行时间小于1秒

运行:

1。JobA和JobB相继启动

2。几秒钟后JobA数量达到10,其间JobB被执行若干次,则新的JobA和JobB均不能被启动,调度器进入阻塞状态

3。30秒后,JobA(0-9)相继执行完毕,新的JobA和JobB均有机会被重新启动,短时间内,再次进入2的阻塞状态

如何解决调度器阻塞问题?

1、 延长可能需要较长时间执行的JOB的时间间隔,假设Job执行时间最大时间为t1, 两次任务执行间隔调度时间为d1, 则d1>t1

2、 使用有状态调度任务StatefulJob代替没有状态的Job. 对于要求执行间隔时间尽可能短,又不希望造成阻塞的比较适合。可以同时有无状态的调度任务JobA,和有状态的调度任务JobB,JobB堵塞后不会对JobA造成影响,即读报文的任务阻塞了,不会对调度器中其他任务造成影响,同时JobA执行完后,可再次继续下一个任务。

如果JobA执行时间较长的话,可能造成JobA始终占用一个线程资源。

3、注意:一个调度器中如果有很多个Job(JobA,JobB,JobC…),其中有一个很容易堵塞,则该Job也会造成其他的Job阻塞

线程池大小配置在org.quartz下的quartz.properties文件中

org.quartz.threadPool.threadCount = 10

如若要修改线程池的大小,可以修改该文件中的 org.quartz.threadPool.threadCount值。亦可建一org.quartz包,包下放置quartz.properties文件,覆盖掉quartz.jar中的配置

但是,修改线程池的大小并不能解决调度器阻塞问题,因为资源消耗的速度不及资源释放的速度时,资源就会被耗尽。

阻塞的原因:

两次JOB不是stateFul(实现stateFul接口,意义为防止并发, 即要串行,上一次执行完此次才执行)的,而job执行时间大于间隔时间,会导致阻塞. 异常情况为Job执行时间过长,

1: 外部接口调用没有设置超时时间, 一直阻塞.
在项目中使用了quartz的分布式功能,两个节点协调运行定时任务,近期发现quartz的有个定时任务经常会一直处于blocked堵塞状态,后续一直都没有再触发,看日志也没任何错误信息,一直在纳闷。经过一系列的研究,发现定时任务的逻辑里是需要通过http请求外部系统的,使用的apache的httpclient框架,当时没有设置连接超时和读取超时时间,仔细看了httpclient框架的源码,底层调用socket的时候,可以设置超时时间,会一直堵塞等待数据的返回,导致整个线程都堵塞了。终于明白,其实定时任务时根本没有执行完毕的,只是线程一直被堵塞了,这个坑巨大啊。有使用http、ftp、socket等进行通信的,一定要设置超时时间,不然,就真的是出麻烦。

2.事务未提交, 锁就不会释放, 导致下次Job进来,锁等待超时
①事务未提交
在使用spring test 做单元测试时,debug导致事务没有提交,Lock wait timeout exceeded;
定位到该表,直接navicat 上insert 该表,Lock wait timeout exceeded;
确认是该表的事务没有提交,锁被占用没释放:
select * from information_schema.innodb_trx;
查看到堵塞的线程,kill trx_mysql_thread_id,

②表级锁被阻塞
同事在查询数据库数据写入到excel时,数据量较大有200万,未能正确分页,导致数据库锁等待卡死;
show processList;
查看到多个线程处于sending data的状态,
kill掉 线程,恢复数据库;
并将存储引擎由MyIsam 改为 INnnoDB,可减少表级锁。

上一篇:常见定时器学习


下一篇:Quartz整合Spring