一、Quartz 基本介绍
1.1 Quartz特点
Quartz
具有以下特点:
- 强大的调度功能,例如支持丰富多样的调度方法,可以满足各种常规及特殊需求;
- 灵活的应用方式,例如支持任务和调度的多种组合方式,支持调度数据的多种存储方式;
- 分布式和集群能力,Terracotta 收购后在原来功能基础上作了进一步提升。
另外,作为 Spring 默认的调度框架,Quartz 很容易与 Spring 集成实现灵活可配置的调度功能。
quartz调度核心元素:
- Scheduler: 任务调度器,是实际执行任务调度的控制器。在spring中通过
SchedulerFactoryBean
封装起来。 - Trigger:触发器,用于定义任务调度的时间规则,有
SimpleTrigger
,CronTrigger
,其中CronTrigger用的比较多。CronTrigger在spring中封装在CronTriggerFactoryBean中。 - JobDetail: 用来描述Job实现类及其它相关的静态信息,如Job名字、关联监听器等信息。在spring中有
JobDetailFactoryBean
和MethodInvokingJobDetailFactoryBean
两种实现,如果任务调度只需要执行某个类的某个方法,就可以通过MethodInvokingJobDetailFactoryBean
来调用。 - Job: 是一个接口,只有一个方法
void execute(JobExecutionContext context)
,开发者实现该接口定义运行任务,JobExecutionContext
类提供了调度上下文的各种信息。Job运行时的信息保存在JobDataMap
实例中。实现Job接口的任务,默认是无状态的,若要将Job设置成有状态的,在quartz中是给实现的Job添加@DisallowConcurrentExecution
注解(以前是实现StatefulJob接口,现在已被Deprecated),在与spring结合中可以在spring配置文件的job detail中配置concurrent参数。 - QuartzSchedulerResources:相当于调度的资源存放器,包含了JobStore, ThreadPool等资源,调度都是通过 QuartzSchedulerResources获取相关属性的。
实现一个最简单的 Quartz 定时任务(不支持多机),有几个步骤:
- 创建 Job。
- 创建 JobBuilder。顾名思义,可以用于生成 JobDetail 。
- 创建 TriggerBuilder。作用:配置定时时间,可以用于生成 Trigger 。
- 创建 Scheduler。作用:启动定时任务。
|
|
1.2 Quartz 集群配置
quartz
集群是通过数据库表来感知其他的应用的,各个节点之间并没有直接的通信。只有使用持久的JobStore才能完成Quartz集群。
数据库表:以前有12张表,现在只有11张表,现在没有存储listener相关的表,多了QRTZ_SIMPROP_TRIGGERS
表:
Table name | Description |
---|---|
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_TRIGGERS | Trigger作为Blob类型存储 |
QRTZ_TRIGGERS | 存储已配置的Trigger的信息 |
QRTZ_SIMPROP_TRIGGERS |
QRTZ_LOCKS
就是Quartz集群实现同步机制的行锁表,包括以下几个锁:CALENDAR_ACCESS
、JOB_ACCESS
、MISFIRE_ACCESS
、STATE_ACCESS
、TRIGGER_ACCESS
。
二、Quartz 原理及流程
2.1 quartz基本原理
Quartz是通过对用户暴露出Scheduler来进行任务的操作,它可以把任务JobDetail和触发器Trigger加入任务池中,可以把任务删除,也可以把任务停止,scheduler把这些任务和触发器放到一个JobStore中,这里jobStore有内存形式的也有持久化形式的.
它内部会通过一个调度线程QuartzSchedulerThread
不断到JobStore
中找出下次需要执行的任务,并把这些任务封装放到一个线程池ThreadPool
中运行.
在 Quartz 中, scheduler 由 scheduler 工厂创建:DirectSchedulerFactory
或者 StdSchedulerFactory
。 第二种工厂 StdSchedulerFactory 使用较多,因为 DirectSchedulerFactory 使用起来不够方便,需要作许多详细的手工编码设置。 Scheduler 主要有三种:RemoteMBeanScheduler
, RemoteScheduler
和 StdScheduler
。以最常用的 StdScheduler
为例讲解。
Quartz 核心元素之间的关系如下图所示:
图 1. Quartz 核心元素关系图
线程视图
在 Quartz 中,有两类线程,Scheduler
调度线程和任务执行线程,其中任务执行线程通常使用一个线程池维护一组线程。
图 2. Quartz 线程视图
Scheduler 调度线程主要有两个: 执行常规调度的线程,和执行 misfired trigger 的线程。常规调度线程轮询存储的所有 trigger,如果有需要触发的 trigger,即到达了下一次触发的时间,则从任务执行线程池获取一个空闲线程,执行与该 trigger 关联的任务。Misfire 线程是扫描所有的 trigger,查看是否有 misfired trigger,如果有的话根据 misfire 的策略分别处理。下图描述了这两个线程的基本流程:
图 3. Quartz 调度线程流程图
图 4. Quartz 调度执行时序图
2.2 quartz源码分析
StdSchedulerFactory.getScheduler()
源码
|
|
上面有个过程是初始化jobStore,表示使用哪种方式存储scheduler相关数据。quartz有两大jobStore:RAMJobStore
和JDBCJobStore
。RAMJobStore
把数据存入内存,性能最高,配置也简单,但缺点是系统挂了难以恢复数据。JDBCJobStore
保存数据到数据库,保证数据的可恢复性,但性能较差且配置复杂。
QuartzScheduler.scheduleJob(JobDetail, Trigger)
源码
|
|
QuartzScheduler.start()
源码
|
|
如何采用多线程进行任务调度
QuartzSchedulerThread.java
|
|
WorkerThread.java
|
|
核心代码就是在while循环中调用Object.wait()
,等待可以跳出while循环的条件成立,当条件成立时,立马调度Object.notifyAll()
使线程跳出while。通过这样的代码,可以实现调度器线程等待启动、工作线程等待job等功能。