绑定完请刷新页面
取消
刷新

分享好友

×
取消 复制
一次quartz非典型异常排查记录
2019-12-09 19:33:51

一次quartz非典型异常排查记录

问题

本地部署定时器任务能正常执行,发布到测试环境就不执行.

排查

通过观察数据库表triggers,周期任务若执行成功,其TRIGGER_STATE字段的值为WAITING,反之则为ERROR,当时觉着关键就在这了,于是网上搜索ERROR的原因.有一个说法我比较认同,说是有不止一个应用实例在操作quartz数据库!我想想,原因可能是这样:假设A实例往库里插入了定时任务,触发器到点了却被B实例(或C,D,E,F,G…)触发;

比如我在A实例代码里封装的JobDataMap和JobDetail,此时B实例欲执行定时任务必须先拿到在A实例封装好的JobDetail,然而B实例尝试去获取的时候就会出错,因为在反序列化JobDataMap对象时,quartz底层会发现B实例并没有DetectionParam,DetectionContext等对象或属性!

解决方案

方案一

让A实例连接一个特定的quartz数据库,保证A实例创建的定时任务只能由A触发,这种方案验证可行.然而不灵活,如果存在n个实例,也要配置n个quartz数据库;

方案二

*思考:*为什么A实例创建的定时任务能被连接同一个库的别的应用实例触发?

假设1:quartz有一套轮询机制,类似Nginx的负载均衡一样;这种解释说得通,但仔细一想就有问题,谁来充当分发任务的媒介?每个quartz都是独立部署,没有管理每个quartz的第三方应用存在;

*假设2:*每个应用实例都会开启一个线程不断地去访问数据库,找出即将满足触发条件的定时任务;如果假设2成立,那么我们只要控制每个实例只能从数据库里查询到属于自己创建的定时任务就行了;

接下来就是验证假设2是否成立,这得深入源码去探个究竟:

步骤:

1.既然是线程,规范的命名是以Thread结尾,于是打开quartz-2.3.0jar包,翻开目录,很快就发现有QuartzSchedulerThread这么一个类,如图:

2.进入这个类,看到有这么一段注释,意思是这是一个执行QuartzScheduler注册的触发器的线程:

3.既然要执行触发器,首先得从数据库里查询出满足条件的触发器,进入run()方法内部,有这么一段代码:

4.见名知意,List集合装的泛型类型翻译过来是"可操作的触发器",那么triggers就表示所有查询出来的待执行触发器;红色方框的那段代码继续跟踪下去会发现最终执行的是StdJDBCConstants中的这段sql语句:

5.将当前时间和triggers表中所有记录的NEXT_FIRE_TIME字段值进行比较,如果大于该时间则执行该trrigers记录对应的任务,并且COL_SCHEDULER_NAME = SCHED_NAME_SUBST,定位 SCHED_NAME_SUBST这个常量值,发现它是长这样的:

很明显,这个值是读取配置文件得来的,而我们起初在quartz.properties文件中并没有配置SCHEDULER_NAME,所以这个字段存到数据库里是有一个默认值:QuartzScheduler!到这里,就不难理解为什么A实例创建的定时任务会被别的实例给触发,原因是它们的SCHEDULER_NAME都是一样的,这就导致不属于自己的触发器也查询出来,然后根据trigger找到对应的JobDetail就出错了,TRIGGER_STATE字段值变为ERROR,触发器执行失败.

6.回到QuartzSchedulerThread类,我们知道这是一个执行触发器的线程,这个线程因为要时刻与数据库交互,所以它应该是随着应用启动而存在的,我首先想到的是通过监听器来实现,查看QuartzSchedulerThread引用位置,定位到QuartzScheduler这个类,看到这么一段注释:

7.这是Quartz的核心,它是Scheduler的一个间接实现,既然是核心,那么一定有料,继续追踪引用,来到StdSchedulerFactory这个类,一个根据quartz.properties文件创建QuartzScheduler实例的工厂:

8.通过定位构造器的引用位置,最终来到了QuartzInitializerListener,随着应用启动创建了StdSchedulerFactory,从而实例化QuartzScheduler,通过QuartzScheduler实例化QuartzSchedulerThread,然后交由线程执行器管理执行;

验证

步骤:

1.同时在本地启动msmp和source-cp两个项目,SCHEDULER_NAME都是默认值,quartz连的都是localhost→quartz,创建三个触发器,这三个触发器会在同一时刻被触发:

2.触发器执行之后,triggers表状态如下:

3.说明只有第一个触发器是被source-cp触发,其他两个都被msmp触发导致触发器执行错误;

4.给source-cp的quartz.properties文件添加一条配置 org.quartz.scheduler.instanceName=QuartzScheduler_SourceCp

5.同样,创建三个触发器,这三个触发器会在同一时刻被触发:

6…触发器执行之后,triggers表状态如下:

7.说明3个触发器都被source-cp触发,且都执行成功,再看源码检测的状态也是检测成功(绿色是没加配置之前的检测结果)!

————————————————

版权声明:本文为CSDN博主「漠狐烟」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/IDale/article/details/100162822

分享好友

分享这个小栈给你的朋友们,一起进步吧。

动力小刚的技术小栈
创建时间:2019-06-10 10:44:56
周传胜/网名动力小刚,6年oracle/mysql/sqlserver经验。工作16年来撰写“操作系统、主机、虚拟化、数据库、存储、云计算、网络、安全”等IT系统生命周期档案、工作学习总结近2万页。个人邮箱zcs0237#163.com欢迎交流。
展开
订阅须知

• 所有用户可根据关注领域订阅专区或所有专区

• 付费订阅:虚拟交易,一经交易不退款;若特殊情况,可3日内客服咨询

• 专区发布评论属默认订阅所评论专区(除付费小栈外)

栈主、嘉宾

查看更多
  • zcs0237
    栈主

小栈成员

查看更多
  • hwayw
  • Ys.
  • 一号管理员
  • 栈栈
戳我,来吐槽~