spring quartz 原理分析和集群技术

基本结构

org.springframework.scheduling.quartz.SchedulerFactoryBean 定义一个 quartz 工厂,包含配置属性,数据源等基本信息

org.quartz.impl.StdScheduler(org.quartz.Scheduler) = #SchedulerFactoryBean. getObject() 是 quartz 实例,其本质是代理了一个 org.quartz.core.QuartzScheduler 对象

QuartzScheduler 对象管理整个定时调度的执行,包含的主要对象,resources 资源对象,管理 JobStore 和 ThreadPool,schedThread 定时调度管理对象,检查 JobStore 中 trigger 是否可以执行了,如果可执行则从 ThreadPool 中查找可用线程执行对应的 Job.

private QuartzSchedulerResources resources;

private QuartzSchedulerThread schedThread;

resources 管理的 org.springframework.scheduling.quartz.LocalDataSourceJobStore 对象,通过与数据库的交互,是定时调度任务获取、执行、更新的实际执行者。同时,JobStore 关联两个线程完成集群管理和 misFire 的管理:MisfireHandler、ClusterManager。

线程

“QuartzScheduler_mestarSchedule##-p-DWUBG6ZTQQNXZLQ1538115231847_MisfireHandler”

“QuartzScheduler_mestarSchedule##-p-DWUBG6ZTQQNXZLQ1538115231847_ClusterManager”

“mestarSchedule##-p_QuartzSchedulerThread”

“mestarSchedule##-p_Worker-20”

“mestarSchedule##-p_Worker-19”

 

线程 1:QuartzSchedulerThread

触发 Job 的执行和更新 trigger 的下次执行时间,其基本原理是,获取 QRTZ_TRIGGERS 表中所有 NEXT_FIRE_TIME 小于当前系统时间的 trigger,如果系统资源 (线程) 足够,且能够获取数据库行级锁 TRIGGER_ACCESS,则更新 trigger 的状态为 ACQUIRED/EXECUTING,并提交执行线程运行对应的 Job,更新 trigger 的 NEXT_FIRE_TIME 后释放行级锁 TRIGGER_ACCESS,所以在集群环境下,quartz 的复杂均衡策略是随机竞争,先获取先执行。

Thread[mestarSchedule##-p_QuartzSchedulerThread,5,QuartzScheduler:mestarSchedule##-p]

image.png

线程 2:ClusterManager

集群管理的作用是检查集群中的其他节点是否正常运行,即心跳检测,但检测机制是通过数据库的时间戳实现的,结点之间并不互相通信。其基本原理是:每个节点会定时在 QRTZ_SCHEDULER_STATE 表中更新 LAST_CHECKIN_TIME 和 CHECKIN_INTERVAL 字段,则其它节点通过判断 LAST_CHECKIN_TIME + CHECKIN_INTERVAL 是否小于当前系统时间判断这个节点是否正常存活,如果判定该节点失效,则接管该节点正在执行的任务。

Thread[QuartzScheduler_mestarSchedule##-p-DESKTOP-8NIA0TS1538970104346_ClusterManager,7,main]

image.png
image.png

线程 3:MisfireHandler

由于 QRTZ_TRIGGERS 的 NEXT_FIRE_TIME 字段是在这个 trigger 被触发执行后才会更新为”NEXT_FIRE_TIME + 间隔时间”的值,那么如果 job 的执行时间大于间隔时间,则会导致 NEXT_FIRE_TIME 的偏差越来越大。例如定时间隔 3 秒,任务执行需要 10 秒,则

image.png

misfireHandle 用于管理上述场景,并对运行时刻进行调整。

Thread[QuartzScheduler_mestarSchedule##-p-DESKTOP-8NIA0TS1538970104346_MisfireHandler,5,main]

image.png
image.png

线程 4:任务执行线程

执行具体的 Job 任务

Thread [mestarSchedule##-p_Worker-1]

image.png

对象

image.png
image.png
image.png
image.png
image.png
image.pngimage.png

 

问题

问题 1:数据库锁

由于 quartz 在执行任务时需要先获取数据库锁,如果异常情况下导致锁一直占用而没有正常释放,则可能导致任务不执行。

 

问题 2:时间同步

quartz 在判断触发时间点、节点状态时,会用触发时间、节点的时间和本机时间比较,如果节点之间的系统时间相差较大,则可能导致节点和任务状态的判断不准确,也可能导致任务执行异常。

数据库表

select * from mtb_job

select * from QRTZ_JOB_DETAILS t

–select * from QRTZ_BLOB_TRIGGERS t

–select * from QRTZ_CALENDARS t

select * from QRTZ_CRON_TRIGGERS t

select * from QRTZ_FIRED_TRIGGERS t

select * from QRTZ_LOCKS t

–select * from QRTZ_PAUSED_TRIGGER_GRPS t

select * from QRTZ_SCHEDULER_STATE t

–select * from QRTZ_SIMPLE_TRIGGERS t

–select * from QRTZ_SIMPROP_TRIGGERS t

select * from QRTZ_TRIGGERS t

 

select * from QRTZ_LOCKS t WHERE lock_name=‘STATE_ACCESS’ FOR UPDATE NOWAIT;

select * from QRTZ_LOCKS t WHERE lock_name=‘TRIGGER_ACCESS’ FOR UPDATE NOWAIT;

 

select t2.username,

t2.sid,

t2.serial#,

t3.object_name,

t2.OSUSER,

t2.MACHINE,

t2.PROGRAM,

t2.LOGON_TIME,

t2.COMMAND,

t2.LOCKWAIT,

t2.SADDR,

t2.PADDR,

t2.TADDR,

t2.SQL_ADDRESS,

t1.LOCKED_MODE

from v$locked_object t1, v$session t2, dba_objects t3

 where t1.session_id = t2.sid

and t1.object_id = t3.object_id

 order by t2.logon_time;

   

select f.sched_name, f.entry_id, f.priority, f.state, t.NEXT_FIRE_TIME, t.PREV_FIRE_TIME, t.priority,t.TRIGGER_STATE,j.JOB_NAME,j.JOB_CLASS_NAME,c.trigger_name,c.CRON_EXPRESSION,m.JOB_NAME

from QRTZ_FIRED_TRIGGERS f join QRTZ_TRIGGERS t on f.trigger_name = t.trigger_name join QRTZ_JOB_DETAILS j

 on t.JOB_NAME = j.JOB_NAME join QRTZ_CRON_TRIGGERS c on t.trigger_name = c.trigger_name join mtb_job m on j.JOB_NAME = m.gid

  

select t.NEXT_FIRE_TIME, t.PREV_FIRE_TIME, t.priority,t.TRIGGER_STATE,j.JOB_NAME,j.JOB_CLASS_NAME,c.trigger_name,c.CRON_EXPRESSION,m.JOB_NAME

from QRTZ_TRIGGERS t

join QRTZ_JOB_DETAILS j on t.JOB_NAME = j.JOB_NAME

join QRTZ_CRON_TRIGGERS c on t.trigger_name = c.trigger_name

left join mtb_job m on j.JOB_NAME = m.gid