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]
线程 2:ClusterManager
集群管理的作用是检查集群中的其他节点是否正常运行,即心跳检测,但检测机制是通过数据库的时间戳实现的,结点之间并不互相通信。其基本原理是:每个节点会定时在 QRTZ_SCHEDULER_STATE 表中更新 LAST_CHECKIN_TIME 和 CHECKIN_INTERVAL 字段,则其它节点通过判断 LAST_CHECKIN_TIME + CHECKIN_INTERVAL 是否小于当前系统时间判断这个节点是否正常存活,如果判定该节点失效,则接管该节点正在执行的任务。
Thread[QuartzScheduler_mestarSchedule##-p-DESKTOP-8NIA0TS1538970104346_ClusterManager,7,main]
线程 3:MisfireHandler
由于 QRTZ_TRIGGERS 的 NEXT_FIRE_TIME 字段是在这个 trigger 被触发执行后才会更新为”NEXT_FIRE_TIME + 间隔时间”的值,那么如果 job 的执行时间大于间隔时间,则会导致 NEXT_FIRE_TIME 的偏差越来越大。例如定时间隔 3 秒,任务执行需要 10 秒,则
misfireHandle 用于管理上述场景,并对运行时刻进行调整。
Thread[QuartzScheduler_mestarSchedule##-p-DESKTOP-8NIA0TS1538970104346_MisfireHandler,5,main]
线程 4:任务执行线程
执行具体的 Job 任务
Thread [mestarSchedule##-p_Worker-1]
对象
问题
问题 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