Quartz基本原理

转载请注明出处:https://www.cnblogs.com/Dorae/p/9357180.html

 

1. 核心元素

Quartz核心要素有Scheduler、Trigger、Job、JobDetail,其中trigger和job、jobDetail为元数据,而Scheduler为实际进行调度的控制器。

  • Trigger

Trigger用于定义调度任务的时间规则,在Quartz中主要有四种类型的Trigger:SimpleTrigger、CronTrigger【重点】、DataIntervalTrigger和NthIncludedTrigger。

  • Job&Jodetail

Quartz将任务分为Job、JobDetail两部分,其中Job 用来定义任务的执行逻辑,而JobDetail用来描述Job的定义,在Quartz中主要有两种类型的Job,StateLessJob、StateFulJob。

  • Scheduler

实际执行调度逻辑的控制器,Quartz提供了DirectSchedulerFactory和StdSchedulerFactory等工厂类,用于支持Scheduler相关对象的产生。

 

2. 核心元素间的关系

3. 主要线程

在Quartz中,有两类线程:执行线程和调度线程,其中执行任务的线程通常由一个线程池维护,关系如下:
 
 

regular Scheduler Thread:执行常规调度

Misfire Scheduler Thread:执行错失的任务

其中 regular Thread 轮询 Trigger,如果有将要触发的Trigger,则从任务线程池中获取一个空闲的线程,然后执行与Trigger关联的Job,Misfire Thread则是扫描所有的Trigger,查看是否有错失的,如果有,根据一定的策略进行处理。

 

4. 数据存储

Quartz中的 Job和 Trigger需要存储下来才能被使用。Quartz有两种存储方式:RAMJobStore,JobStoreSupport.

RAMJobStore: 将 Job 和 Trigger存储在内存中,性能高,但是服务一旦重启则会丢失。

JobStoreSupport:基于jdbc将trigger和job存储到数据库中,数据不会丢失。

Table nameDescription
QRTZ_CALENDARS存储Quartz的Calendar信息
QRTZ_CRON_TRIGGERS存储CronTrigger,包括Cron表达式和时区信息
QRTZ_FIRED_TRIGGERS存储与已触发的Trigger相关的状态信息,以及相联Job的执行信息
QRTZ_PAUSED_TRIGGER_GRPS存储已暂停的Trigger组的信息
QRTZ_SCHEDULER_STATE存储少量的有关Scheduler的状态信息,和别的Scheduler实例
QRTZ_LOCKS存储程序的悲观锁的信息
QRTZ_JOB_DETAILS存储每一个已配置的Job的详细信息
QRTZ_SIMPLE_TRIGGERS存储简单的Trigger,包括重复次数、间隔、以及已触的次数
QRTZ_BLOG_TRIGGERSTrigger作为Blob类型存储
QRTZ_TRIGGERS存储已配置的Trigger的信息
QRTZ_SIMPROP_TRIGGERS存储CalendarIntervalTrigger和DailyTimeIntervalTrigger两种类型的触发器

 

4. Quartz集群原理

一个Quartz集群中的每个节点是一个独立的Quartz应用,它又管理着其他的节点。

这就意味着你必须对每个节点分别启动或停止。

Quartz集群中,独立的Quartz节点并不与其他节点或者管理节点通信,而是通过数据库来感知到另一个Quartz应用,如图:

 

5. Quartz主要流程

  • 启动流程

若Quartz是配置在spring中,当服务器启动时,就会装载相关的bean。SchedulerFactoryBean实现了InitializingBean接口,因此在初始化Bean的时候会执行afterPropertiesSet,该方法会调用SchedulerFactory(DirectSchedulerFactory 或者 StdSchedulerFactory,通常用StdSchedulerFactory)创建Scheduler,SchedulerFactory在创建quartzScheduler的过程中,将会读取配置参数,初始化各个组件,关键组件如下:

  1. ThreadPool:一般是使用SimpleThreadPool,SimpleThreadPool创建了一定数量的WorkerThread实例来使得Job能够在线程中进行处理。WorkerThread是定义在SimpleThreadPool类中的内部类,它实质上就是一个线程。在SimpleThreadPool中有三个list:workers-存放池中所有的线程引用,availWorkers-存放所有空闲的线程,busyWorkers-存放所有工作中的线程;
    线程池的配置参数如下所示:

    org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
    org.quartz.threadPool.threadCount=3
    org.quartz.threadPool.threadPriority=5

  2. JobStore:分为存储在内存的RAMJobStore和存储在数据库的JobStoreSupport(包括JobStoreTX和JobStoreCMT两种实现,JobStoreCMT是依赖于容器来进行事务的管理,而JobStoreTX是自己管理事务),若要使用集群要使用JobStoreSupport的方式;

  3. QuartzSchedulerThread:用来进行任务调度的线程,在初始化的时候paused=true,halted=false,虽然线程开始运行了,但是paused=true,线程会一直等待,直到start方法将paused置为false;

另外,SchedulerFactoryBean还实现了SmartLifeCycle接口,因此初始化完成后,会执行start()方法,该方法将主要会执行以下的几个动作:

  1. 创建ClusterManager线程并启动线程:该线程用来进行集群故障检测和处理,将在下文详细讨论;
  2. 创建MisfireHandler线程并启动线程:该线程用来进行misfire任务的处理,将在下文详细讨论;
  3. 置QuartzSchedulerThread的paused=false,调度线程才真正开始调度;
 
  •  QuartzSchedulerThread线程

QuartzSchedulerThread线程是实际执行任务调度的线程,其中主要代码如下。

while (!halted.get()) {
	int availThreadCount = qsRsrcs.getThreadPool().blockForAvailableThreads();
	triggers = qsRsrcs.getJobStore().acquireNextTriggers(now + idleWaitTime,
			Math.min(availThreadCount, qsRsrcs.getMaxBatchSize()), qsRsrcs.getBatchTimeWindow());
	long triggerTime = triggers.get(0).getNextFireTime().getTime();
	long timeUntilTrigger = triggerTime - now;
	while (timeUntilTrigger > 2) {
		now = System.currentTimeMillis();
		timeUntilTrigger = triggerTime - now;
	}
	List<TriggerFiredResult> bndle = qsRsrcs.getJobStore().triggersFired(triggers);
	for (int i = 0; i < res.size(); i++) {
		JobRunShell shell = qsRsrcs.getJobRunShellFactory().createJobRunShell(bndle);
		shell.initialize(qs);
		qsRsrcs.getThreadPool().runInThread(shell);
	}
}
 

1:先获取线程池中的可用线程数量(若没有可用的会阻塞,直到有可用的);

2:获取30m内要执行的trigger(即acquireNextTriggers):
     获取trigger的锁,通过select …for update方式实现;获取30m内(可配置)要执行的triggers(需要保证集群节点的时间一致),若@ConcurrentExectionDisallowed且列表存在该条trigger则跳过,否则更新trigger状态为ACQUIRED(刚开始为WAITING);插入firedTrigger表,状态为ACQUIRED;(注意:在RAMJobStore中,有个timeTriggers,排序方式是按触发时间nextFireTime排的;JobStoreSupport从数据库取出triggers时是按照nextFireTime排序);

3:等待直到获取的trigger中最先执行的trigger在2ms内;

4:triggersFired:

  1. 更新firedTrigger的status=EXECUTING;
  2. 更新trigger下一次触发的时间;
  3. 更新trigger的状态:无状态的trigger->WAITING,有状态的trigger->BLOCKED,若nextFireTime==null ->COMPLETE;
  4. commit connection,释放锁;

5:针对每个要执行的trigger,创建JobRunShell,并放入线程池执行:

  1. execute:执行job
  2. 获取TRIGGER_ACCESS锁
  3. 若是有状态的job:更新trigger状态:BLOCKED->WAITING,PAUSED_BLOCKED->BLOCKED
  4. 若@PersistJobDataAfterExecution,则updateJobData
  5. 删除firedTrigger
  6. commit connection,释放锁
 
 

调度过程中Trigger状态变化如图所示:

 

 
 
  • MisfireHandler线程

下面这些原因可能造成 misfired job

1、系统被重启,再重启的一段时间内,可能有些任务没有被及时处理

2、Trigger 被暂停了一段时间

3、线程池所有的资源被占用,没有空闲的线程可被支配使用

4、任务在还没有执行结束时,已经到达下次执行时间

Quartz 中为 trigger 定义了处理策略,主要有下面两种:

MISFIRE_INSTRUCTION_FIRE_ONCE_NOW:针对 misfired job 马上执行一次;

MISFIRE_INSTRUCTION_DO_NOTHING:忽略 misfired job,等待下次触发;默认是

执行流程:

  1. 若配置(默认为true,可配置)成获取锁前先检查是否有需要recovery的trigger,先获取misfireCount;
  2. 获取TRIGGER_ACCESS锁;
  3. hasMisfiredTriggersInState:获取misfired的trigger,默认一个事务里只能最大20个misfired trigger(可配置),misfired判断依据:status=waiting,next_fire_time < current_time-misfirethreshold(可配置,默认1min)
  4. notifyTriggerListenersMisfired
  5. updateAfterMisfire:获取misfire策略(默认是MISFIRE_INSTRUCTION_SMART_POLICY,该策略在CronTrigger中=MISFIRE_INSTRUCTION_FIRE_ONCE_NOW),根据策略更新nextFireTime;
  6. 将nextFireTime等更新到trigger表;
  7. commit connection,释放锁8.如果还有更多的misfired,sleep短暂时间(为了集群负载均衡),否则sleep misfirethreshold时间,后继续轮询;
 
 

misfireHandler线程执行流程如图1-7所示:

  • ClusterManager集群管理线程

初始化:

failedInstance=failed+self+firedTrigger表中的schedulerName在scheduler_state表中找不到的(孤儿)

线程执行:

每个服务器会定时(org.quartz.jobStore.clusterCheckinInterval这个时间)更新SCHEDULER_STATE表的LAST_CHECKIN_TIME,若这个字段远远超出了该更新的时间,则认为该服务器实例挂了;

注意:每个服务器实例有唯一的id,若配置为AUTO,则为hostname+current_time

线程执行的具体流程:

  1. 检查是否有超时的实例failedInstances;
  2. 更新该服务器实例的LAST_CHECKIN_TIME;
    若有超时的实例:
  3. 获取STATE_ACCESS锁;
  4. 获取超时的实例failedInstances;
  5. 获取TRIGGER_ACCESS锁;
  6. clusterRecover:
    • 针对每个failedInstances,通过instanceId获取每个实例的firedTriggers;
    • 针对每个firedTrigger:
      • 更新trigger状态:
        • BLOCKED->WAITING
        • PAUSED_BLOCKED->PAUSED
        • ACQUIRED->WAITING
      • 若firedTrigger不是ACQUIRED状态(在执行状态),且jobRequestRecovery=true:
        创建一个SimpleTrigger,存储到trigger表,status=waiting,MISFIRE_INSTR=MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY.
      • 删除firedTrigger

 
 
 
 
 
 
 

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值