JDBC-Based JobStore指的是应用数据库长久化存储作业相干信息的JobStore,与之对应的是基于内存的RAMJobStore。

咱们后面学习Quartz的Job、JobDetail、Trigger、作业调度线程以及作业执行线程的时候,大部分状况下是基于RAMJobStore进行剖析的,所以对RAMJobStore曾经有了理解,然而还不太理解JDBC-Based JobStore。

咱们从以下几个角度剖析JDBC-Based JobStore:

  1. 波及到的表以及各表的作用
  2. 作业调度线程以及作业执行线程的基于JDBC-Based JobStore的次要工作过程
  3. JDBC-Based JobStore的事务管理及锁机制
  4. JDBC-Based JobStore的recovery及Misfire解决

以上4点放在一片文章中可能会太长,所以咱们可能会分2篇文章进行剖析,明天先剖析第1/2两个问题。

JDBC-Based JobStore波及到的表

JDBC-Based JobStore波及到的表(省略前缀qrtz_):

  1. JOBDETAILS:作业信息
  2. TRIGGERS:Trigger信息
  3. SIMPLE_TRIGGERS:Simple Trigger信息
  4. CRON_TRIGGERS:Cron Trigger信息
  5. FIRED_TRIGGERS:被触发的Triggers
  6. SCHEDULER_STATE:调度服务的状态
  7. LOCKS:锁

Job及Trigger的注册

注册过程咱们在后面的文章中曾经说过了,RAMJobStore与JDBC-Based JobStore的区别次要是存储形式不同,一个存储在内存中,一个存储在数据库中。

Job注册后存储在JOBDETAILS表中,内容与存储在内存中基本一致,次要包含sched_name、name、group、description、job_class_name、job_data等。

Trigger注册后存储在TRIGGERS表中,内容与存储在内存中的也基本一致,次要包含sched_name、name、group、job_name、job_group、trigger_state、trigger_type、start_time、end_time、calendar_name、misfire_instr、job_data等。

trigger_state在初始化写入之后的状态为WAITING(期待被触发)。

trigger_type包含:

  1. SIMPLE:Simple Trigger
  2. CRON:Cron Trigger
  3. CAL_INT:Calendar Interval Trigger
  4. DAILY_I:Daily Time Interval Trigger
  5. BLOB:A general blob Trigger

不论是jobdetails还是trigger表都有一个sched_name字段,记录以后的任务调度器名称,sched_name从配置文件中取值(org.quartz.scheduler.instanceName)。

这个调度器名称sched_name其实就是运行任务调度服务的服务器标识,也就是以后正在运行quartz的服务的标识,集群环境下,不同的服务必须指定不同的sched_name,无关quartz集群的细节咱们在其余文章做详细分析。

Trigger注册的时候还波及到其余表:
如果以后Trigger处于Group Paused状态,Trigger同时写入paused_trigger_grps表。

Simple Trigger会写入Simple_triggers表,Cron Trigger会写入Cron_triggers表,负责记录各自的非凡属性;Simple_triggers记录repeat_count/repeat_interval/times_triggered等信息,Cron_triggers表记录cron表达式。

作业的调度

调度工作的执行逻辑咱们在后面的文章中曾经重复剖析过:

  1. 从作业执行线程池获取availThreadCount,也就是以后可用的线程数
  2. 调用JobStore的acquireNextTriggers办法,获取特定短时间(idleWaitTime,默认30秒)内可能须要被触发的,数量不超过availThreadCount的触发器
  3. 调用JobStore的triggersFired办法对获取到的可能须要被触发的触发器进行二次加工,再次获取到最终的待触发的触发器后果集
  4. 循环解决最终的待处理触发器后果集中的每一个须要被触发的触发器
  5. 用JobRunShell包装该触发器做绑定的Job,送给线程池执行作业

JobStoreSupport#acquireNextTriggers

JobStoreSupport是JDBC-Based JobStore的抽象类,有两个实现类JobStoreCMT和JobStoreTX,两个实现类的次要作用是治理事务,大部分的业务逻辑其实都是在JobStoreSupport中实现的,包含acquireNextTriggers和triggersFired办法。

acquireNextTriggers办法首先从Triggers表中获取符合条件的触发器:nextFireTime在30秒内(有参数idleWaitTime设定)的、状态为WAITING的、肯定数量的(参数设置的一次解决的触发器个数、或者可用的执行线程数)的触发器。

针对获取到的每一个Trigger:

  1. 从JobDetails表中获取其绑定的Job,判断Job的ConcurrentExectionDisallowed属性,做并发管制(逻辑与RAMJobStore的相干逻辑一样)
  2. Triggers表中以后trigger的状态从WAITING批改为ACQUIRED
  3. 以后Trigger写入到fired_triggers表中,状态为ACQUIRED

JobStoreSupport#triggersFired

再次从triggers表中获取到以后trigger,验证其状态是否为ACQUIRED,状态不正确的不做解决。

从job_details表中获取到以后trigger绑定的作业。

更新fired_triggers表中以后trigger的状态为EXECUTING。

调用trigger的triggered办法获取以后trigger的下一次触发工夫。

更新triggers表中以后trigger的状态(WAITING)、下次触发工夫等数据。

作业执行#JobRunShell

通过以上步骤,作业调度线程就获取到了以后须要执行的trigger,之后就须要执行最初一步:

用JobRunShell包装该触发器,送给线程池执行该触发器关联的作业

咱们须要对这个JobRunShell做一个简略理解。

JobRunShell instances are responsible for providing the 'safe' environment for Job s to run in, and for performing all of the work of executing the Job, catching ANY thrown exceptions, updating the Trigger with the Job's completion code, etc.
A JobRunShell instance is created by a JobRunShellFactory on behalf of the QuartzSchedulerThread which then runs the shell in a thread from the configured ThreadPool when the scheduler determines that a Job has been triggered.

JavaDoc说的十分分明,JobRunShell其实才是最终负责Job运行的,SimpleThreadPool只是提供了运行Job的线程,有工作交进来之后(交进来的其实是持有job对象的JobRunShell对象)SimpleThreadPool负责调配一个执行线程、之后用该线程运行该工作。

咱们在后面剖析SimpleThreadPool的文章中曾经说过,线程池的执行线程WorkThread有一个Runable接口的对象,工作触发后Job会封装为这个Runable对象、而后交给WorkThread用一个新的线程执行这个Runable对象。

这个Runable对象就是JobRunShell,JobRunShell实现了Runable接口。

所以咱们就从JobRunShell的run办法动手。

JobRunShell#run

JobRunShell在初始化的时候封装了一个JobExecutionContextImpl对象jec,其中蕴含了Job、Trigger、Scheduler等相干对象。

从jec获取到job,调用job的execute办法,这里其实就调用到了咱们应用层中实现了Job接口对象的execute办法,其实是咱们的业务逻辑被调用执行了。

作业调用执行实现之后,回调QuartzScheduler的notifyJobStoreJobComplete办法,告诉调度器以后trigger曾经实现执行。

QuartzScheduler的notifyJobStoreJobComplete办法调用JobStore的triggeredJobComplete办法。

JobStoreSuppor#triggeredJobComplete

依据trigger执行状况更新triggers表中以后trigger的状态,如果trigger的nextFireTime不为空的话更新为WAITING,期待下次被触发,如果为空的话则更新为COMPLETED,工作执行实现。除此之外还有其余的挂起、谬误等状态。

以后trigger从fired_triggers表中删除。

Trigger State

Trigger的状态其实和JobStore无关,也就是说不论是用基于内存的JobStore,还是基于数据库的JobStore,对于Trigger状态的治理逻辑都是一样的。

Trigger的状态包含:

  1. WAITING:初始化状态,期待被调度
  2. ACQUIRED:曾经被调度器获取,期待被触发
  3. EXECUTING:正在执行
  4. COMPLETE:执行实现,不再被调度
  5. BLOCKED:阻塞
  6. ERROR:谬误
  7. PAUSED:挂起/暂停
  8. PAUSED_BLOCKED:挂起-阻塞
  9. DELETED:被删除

Trigger注册的时候,如果在挂起列表(qrtz_paused_trigger_grps)中的话,状态为PAUSED,否则状态为WAITING。

Trigger被调度器调度之后(状态为WAITING、nextFiredTime在30秒之内)状态为ACQUIRED,被作业执行线程调起执行的时候状态变更为EXECUTING,作业执行实现后,如果Trigger不再须要被执行(执行次数或执行工夫达到了设置要求)则状态为COMPLETE。

设置为ConcurrentExectionDisallowed的作业被调度器执行后,以后作业绑定的其余Trigger的状态:

  1. WAITING -> BLOCKED
  2. ACQUIRED -> BLOCKED
  3. PAUSED -> PAUSED_BLOCKED

作业被调度器调度执行后,以后Trigger的状态:

  1. 如果以后Trigger被触发后,nextFiredTime不为空的话,状态设置为WAITING,期待下次被触发
  2. 否则,设置为COMPLETE,触发器完成使命

JobRunShell实现作业执行后通过调用notifyJobStoreJobComplete办法告诉JobStore触发器实现本次作业调度后,如果以后Trigger绑定的作业设置为ConcurrentExectionDisallowed,则设置以后作业绑定其余Trigger的状态(与作业执行时做相同操作以回复这些触发器到失常状态):

  1. BLOCKED -> WAITING
  2. PAUSED_BLOCKED -> PAUSED

咱们能够通过调用scheduler的pauseTrigger办法暂停/挂起当前任务,调用后触发器的状态:

  1. WAITING -> PAUSED
  2. ACQUIRED -> PAUSED
  3. BLOCKED -> PAUSED_BLOCKED

被暂停的工作被从新调起后,状态复原。

小结

到这儿,开篇设置的本篇文章的工作就实现了,后续的两个问题下一篇文章持续。

Thanks a lot!

上一篇 Quartz 的相干线程