简介:开源任务进度管理器
使用:https://github.com/dufyun/quartz-core-learning QuartzDemo的代码:spring集成quartz;quartz on springboot
一个小Demo:
public class QuartzDemo implements Job{
@Override
public void execute(JobExecutionContext arg0) throws JobExecutionException {
System.out.println("Quartz执行.......");
}
}
/**
* This is a RAM Store Quartz!
*
*/
public class RAMQuartz {
private static Logger _log = LoggerFactory.getLogger(RAMQuartz.class);
public static void main(String[] args) throws SchedulerException {
//1.创建Scheduler的工厂
SchedulerFactory sf = new StdSchedulerFactory();
//2.从工厂中获取调度器实例
Scheduler scheduler = sf.getScheduler();
//3.创建JobDetail
JobDetail jb = JobBuilder.newJob(RAMJob.class)
.withDescription("this is a ram job") //job的描述
.withIdentity("ramJob", "ramGroup") //job 的name和group
.build();
//任务运行的时间,SimpleSchedle类型触发器有效
long time= System.currentTimeMillis() + 3*1000L; //3秒后启动任务
Date statTime = new Date(time);
//4.创建Trigger
//使用SimpleScheduleBuilder或者CronScheduleBuilder
Trigger t = TriggerBuilder.newTrigger()
.withDescription("")
.withIdentity("ramTrigger", "ramTriggerGroup")
//.withSchedule(SimpleScheduleBuilder.simpleSchedule())
.startAt(statTime) //默认当前时间启动
.withSchedule(CronScheduleBuilder.cronSchedule("0/2 * * * * ?")) //两秒执行一次
.build();
//5.注册任务和定时器
scheduler.scheduleJob(jb, t);
//6.启动 调度器
scheduler.start();
_log.info("启动时间 : " + new Date());
}
}
Quartz框架中用到的设计模式:
工厂模式,静态工厂
单例模式:懒汉式,饿汉式(还可以改进)
桥接模式:
quartz的原理
Quartz基础组件:
实现了org.quartz.Scheduler
接口的StdSchedule实际只是QuartzScheduler的静态代理,后者实现了Scheduler
所有操作。
(一)ScheduleFactory:Schedule的创建
- 从StdScheduleFactory来看,这个工厂类做了什么?
- 读取配置文件, 配置文件中需要配置scheduler、线程池(JobPool)、jobStore、jobListener、triggerListenner、插件等。配置文件的读取过程如下:
读取参数系统参数(启动参数)System中配置的org.quartz.properties指定的文件; 如果找不到则读取项目Classpath目录下的quartz.properties配置文件; 如果找不到则读取quartz jar包中默认的配置文件quartz.properties;
- 从SchedulerRepository中根据名称读取已经创建的scheduler,
- 如果没有则重新创建一个,并保存在SchedulerRepository中。
- 类中的常量:配置文件中的配置项key
- 构造方法:
//一个很重要的属性:
private PropertiesParser cfg;
//指定配置文件(以Properties对象形式配置,或者指定quartz.properties文件路径,或使用无参构造方法)
public StdSchedulerFactory() {
}
public StdSchedulerFactory(Properties props) throws SchedulerException {
initialize(props);//调用初始化方法,直接初始化cfg属性
}
public StdSchedulerFactory(String fileName) throws SchedulerException {
initialize(fileName);//调用初始化方法先加载解析配置文件
}
- 初始化方法:
initialize();initialize(InputStream);initialize(String);分别代表从(当前线程工作空间,从resource资源路径下,从Classpath类路径下加载quartz.propertites配置文件;从命令行启动参数指定的路径下)读取配置;从打开的File文件流中读取配置;从指定路径下读取配置;最后都是将配置文件解析为Properties对象,调用initialize(Properties)初始cfg属性;
- 工厂方法(作为Schedule的工厂,最重要是提供Schedule实例:实例工厂接口)
public static Scheduler getDefaultScheduler() throws SchedulerException {
StdSchedulerFactory fact = new StdSchedulerFactory();
return fact.getScheduler();
}
//SchedulerRepository中持有一个静态HashMap,缓存所有的Schedule对象确保唯一性并且防止被GC回收,并且允许全局的查找-所有这些ClassLoader空间
public Scheduler getScheduler(String schedName) throws SchedulerException {
//根据Schedule名称,从缓存中查找Schedule
return SchedulerRepository.getInstance().lookup(schedName);
}
//获取全部已缓存的Schedule实例
public Collection<Scheduler> getAllSchedulers() throws SchedulerException {
return SchedulerRepository.getInstance().lookupAll();
}
public Scheduler getScheduler() throws SchedulerException {
if (cfg == null) {
//调用无参的初始化方法,加载配置文件,初始化cfg
initialize();
}
// Schedule注册器,持有所有Schedule实例
SchedulerRepository schedRep = SchedulerRepository.getInstance();
//先查看已注册的Schedule
Scheduler sched = schedRep.lookup(getSchedulerName());
//如果Schedule已注册且已关闭,移除schedule,
if (sched != null) {
if (sched.isShutdown()) {
schedRep.remove(getSchedulerName());
} else {
return sched;
}
}
/*核心方法:instantiate主要是实例化相关资源对象,最最主要的工作:
JobStore js = null;
ThreadPool tp = null;
QuartzScheduler qs = null;
DBConnectionManager dbMgr = null;
第一 properties注入全部采用的是 set方法反射注入 ,
第二 全部初始化都是 initialize方法
最后暴露Schedule实例
* */
sched = instantiate();
return sched;
}
核心方法instantiate()部分代码:对象的实例化和初始化基本类似,以JobStore为例:
基本步骤:1.从配置中获取org.quartz.jobStore.class 对应的值,即为指定的JobStore实现类全路径
2.反射实例化
3.从配置中获取org.quartz.jobStore前缀的配置项,即为JobStore的属性,通过调用setBeanProps(Object obj, Properties props)方法,进行set方法反射注入
private Scheduler instantiate() throws SchedulerException {
......
// Get JobStore Properties
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//从配置中读取给定的JobStore类名,若未指定则抛出异常
String jsClass = cfg.getStringProperty(PROP_JOB_STORE_CLASS,
RAMJobStore.class.getName());
if (jsClass == null) {
initException = new SchedulerException(
"JobStore class not specified. ");
throw initException;
}
try {
// 加载指定的oJbStore实现类class对象,并调用class对象的newInstance()通过反射方法实例化
// 此时调用的是无参构造方法,属性均未初始化
js = (JobStore) loadHelper.loadClass(jsClass).newInstance();
} catch (Exception e) {
initException = new SchedulerException("JobStore class '" + jsClass
+ "' could not be instantiated.", e);
throw initException;
}
SchedulerDetailsSetter.setDetails(js, schedName, schedInstId);
// 获取配置中前缀为org.quartz.jobStore的配置项,排除前缀为.lockHandler的
tProps = cfg.getPropertyGroup(PROP_JOB_STORE_PREFIX, true, new String[] {PROP_JOB_STORE_LOCK_HANDLER_PREFIX});
try {
// 反射注入jobStore的属性
setBeanProps(js, tProps);
} catch (Exception e) {
initException = new SchedulerException("JobStore class '" + jsClass
+ "' props could not be configured.", e);
throw initException;
}
if (js instanceof JobStoreSupport) {
// 设置自定义锁句柄(信号量),从配置文件中获取 .lockHandler.class 对应的全路径类名
// Install custom lock handler (Semaphore)
String lockHandlerClass = cfg.getStringProperty(PROP_JOB_STORE_LOCK_HANDLER_CLASS);
if (lockHandlerClass != null) {
try {
// 反射实例化指定类(信号量)
Semaphore lockHandler = (Semaphore)loadHelper.loadClass(lockHandlerClass).newInstance();
// 获取前缀为.lockHandler 配置项
tProps = cfg.getPropertyGroup(PROP_JOB_STORE_LOCK_HANDLER_PREFIX, true);
// If this lock handler requires the table prefix, add it to its properties.
if (lockHandler instanceof TablePrefixAware) {
tProps.setProperty(
PROP_TABLE_PREFIX, ((JobStoreSupport)js).getTablePrefix());
tProps.setProperty(
PROP_SCHED_NAME, schedName);
}
try {
// set反射注入属性
setBeanProps(lockHandler, tProps);
} catch (Exception e) {
initException = new SchedulerException("JobStore LockHandler class '" + lockHandlerClass
+ "' props could not be configured.", e);
throw initException;
}
((JobStoreSupport)js).setLockHandler(lockHandler);
getLog().info("Using custom data access locking (synchronization): " + lockHandlerClass);
} catch (Exception e) {
initException = new SchedulerException("JobStore LockHandler class '" + lockHandlerClass
+ "' could not be instantiated.", e);
throw initException;
}
}
}
......
}
(二) JobStore和Schedule存储器
1.Schedule的实例全部存储在单例的ScheduleRepository中;
2.JobStore存储器,保存Job s 和 Trigger s,给QuartzSchedule使用,同时Job and Trigger的key应该是独一无二的.
支持三种存储配置:RAM(内存存储——直接JVM进程的);terracotta (Terracotta是一款由美国Terracotta公司开发的著名开源Java集群平台。);JDBC (MySQL 之类的 ....)
这个接口中定义的都是JobDetail和Trigger的增删改查的一些公共方法(在子类中实现各不相同,在内存中增删改查,在数据库中)
RAMJobStore类中,使用HashMap缓存任务和触发器,一个基于内存的存储, 很简单一堆集合包住就行了 . 主要注意的是 他会将Job
包装一下称为 JobWrapper
protected HashMap<JobKey, JobWrapper> jobsByKey = new HashMap<JobKey, JobWrapper>(1000);
protected HashMap<TriggerKey, TriggerWrapper> triggersByKey = new HashMap<TriggerKey, TriggerWrapper>(1000);
protected HashMap<String, HashMap<JobKey, JobWrapper>> jobsByGroup = new HashMap<String, HashMap<JobKey, JobWrapper>>(25);
protected HashMap<String, HashMap<TriggerKey, TriggerWrapper>> triggersByGroup = new HashMap<String, HashMap<TriggerKey, TriggerWrapper>>(25);
protected TreeSet<TriggerWrapper> timeTriggers = new TreeSet<TriggerWrapper>(new TriggerWrapperComparator());
protected HashMap<String, Calendar> calendarsByName = new HashMap<String, Calendar>(25);
protected Map<JobKey, List<TriggerWrapper>> triggersByJob = new HashMap<JobKey, List<TriggerWrapper>>(1000);
protected final Object lock = new Object();
protected HashSet<String> pausedTriggerGroups = new HashSet<String>();
protected HashSet<String> pausedJobGroups = new HashSet<String>();
protected HashSet<JobKey> blockedJobs = new HashSet<JobKey>();
protected long misfireThreshold = 5000l;
protected SchedulerSignaler signaler;
private final Logger log = LoggerFactory.getLogger(getClass());
(三)ThreadPool
要为org.quartz.core.QuartzScheduler提供线程池的类,需要实现此接口。
理想情况下,TheadPool实例应该只用于Quartz。
最重要的是,当方法blockForAvailableThreads()获取可用线程的数量,返回1或者更大值时: 在稍后调用runInThread(Runnable)方法时,池中必须至少有一个可用线程。
如果这个假设不成立,可能会导致额外的JobStore查询和更新,如果使用了集群特性,可能会导致更大的负载不平衡。
(四)QuartzSchedulerResources
resources 资源管理器, 包含job和线程池,整个Quartz 的核心资源, 所有资源都在这里.
(五)QuartzScheduler
Quartz的心脏,包含调用job的方法, 注册listener
// 继承链:QuartzScheduler->RemotableQuartzScheduler->Remote//构造器
/*
* @see QuartzSchedulerResources
* @param resources 核心资源 QuartzSchedulerResources 从构造器中给QuartzScheduler
* @param idleWaitTime:当调度器发现当前没有可出发的触发器时,等待多少时间后再次校验,默认是30s,是个死循环, 如果等待时间过短, 会空转很快
* @param dbRetryInterval 废弃了
*/
public QuartzScheduler(QuartzSchedulerResources resources, long idleWaitTime, @Deprecated long dbRetryInterval)
(六)DBConnectionManager
管理 ConnectionProviders , 提供透明的连接访问 .
此处是桥接模式的实现:JobStore -> DBConnectionManager -> ConnectionProviders有一个管理者管理连接的提供者, 然后JobStore 可以通过他获取连接.
(七)JobListener 和 TriggerListener
所有的监听器都由ListenerManager进行管理;
任务监听器——当JobDetail运行的时候会通知你,通常Schedule的应用是不会使用这个监听器的。
执行流程
triggerFired ->
vetoJobExecution ? false -> jobToBeExecuted - > 调用Job接口中的方法 - > jobWasExecuted
? true -> jobExecutionVetoed
//Job调用前
void jobToBeExecuted(JobExecutionContext context);
// veto 否决的意思 , 由 org.quartz.TriggerListener#vetoJobExecution 决定
void jobExecutionVetoed(JobExecutionContext context);
//Job调用后
void jobWasExecuted(JobExecutionContext context,
JobExecutionException jobException);
使用方法:实现这两个方法,然后配置就行:eg
org.quartz.jobListener.j1.class=com.example.springquartz.MyJobListener
// 比如属性设置可以这么做, 只要实现set方法就可以
// org.quartz.jobListener.l1.name=MyJobListener
org.quartz.triggerListener.t1.class=com.example.springquartz.MyTriggerListener
(八)Scheduler
1. Quartz Scheduler的主接口,对外暴露唯一接口,可以操作所有资源对象,一般使用StdSchedule。 里面包含了 QuartzScheduler , 所以可以操作其他对象.实现类:
2. Scheduler的生命周期开始于其被创建时,结束于shutdown()方法调用。一旦对象创建完成,就可以用来操作Jobs和Triggers,包括添加、删除、查询等。但只有在Scheduler start()被调用后,才会按照Trigger定义的触发规则执行Job的内容。
3. Scheduler的核心功能就是操作Job、Trigger、Calendar、Listener等。包括addXXX、deleteXXX、pauseXXX、resumeXXX等。
(九)核心运作流程 .
在QuartzScheduler的构造方法中传入了
QuartzSchedulerResources,将所有资源都放进去了。org.quartz.core.QuartzScheduler#QuartzScheduler
构造方法是执行的任务的核心 , 就是心脏 . 然后交给了 -> QuartzSchedulerThread
轮询处理;
QuartzSchedulerThread是一个线程类,负责查询并触发Triggers。
public class QuartzSchedulerThread extends Thread {
QuartzSchedulerThread(QuartzScheduler qs, QuartzSchedulerResources qsRsrcs, boolean setDaemon, int threadPrio) {
super(qs.getSchedulerThreadGroup(), qsRsrcs.getThreadName());
........
paused = true;
halted = new AtomicBoolean(false);
}
}
QuartzSchedulerThread#run()方法的主要工作:
- 等待QuartzScheduler启动
- 查询待触发的Trigger
- 等待Trigger触发时间到来
- 触发Trigger
- 循环上述步骤
Quartz暴露出来给用户使用的主要API
- Job 接口,定义需要执行的任务
//仅包含一个执行方法,Scheduler控制在每次Trigger触发时创建Job实例 /** JobExecutionContext对象包含了当前任务执行的上下文环境,包括JobDetail、Trigger以及jobDataMap等。 此方法的实现可能希望在此方法退出之前在{@link JobExecutionContext}上设置一个{@link JobExecutionContext#setResult(Object) result}对象。 结果本身对于Quartz是没有意义的,但是对于监视作业执行的{@link JobListener} 或{@link TriggerListener} 可能是有用的。 */ public interface Job { void execute(JobExecutionContext context)throws JobExecutionException; }
- JobDetail 包含job的基本信息,JobDetail用于保存Job相关的属性信息
JobKey唯一确定了一个Job JobDataMap用于存储Job执行时必要的业务信息 JobDetail保存的仅仅是Job接口的Class对象,而非具体的实例。 JobBuilder负责JobDetail实例的创建,并且JobBuilder提供了链式操作,可以方便的为JobDetail添加额外的信息。
- Trigger 描述Job执行的时间触发规则
TriggerKey(group,name)唯一标识了Scheduler中的Trigger JobKey指向了该Trigger作用的Job 一个Trigger仅能对应一个Job,而一个Job可以对应多个Trigger 同样的,Trigger也拥有一个JobDataMap Priority:当多个trigger拥有相同的触发时间,根据该属性来确定先后顺序,数值越大越靠前,默认5,可为正负数 Misfire Instructions:没来得及执行的机制。同一时间trigger数量过多超过可获得的线程资源,导致部分trigger无法执行。不同类型的Trigger拥有不同的机制。当Scheduler启动时候,首先找到没来得及执行的trigger,再根据不同类型trigger各自的处理策略处理 Calendar:Quartz Calendar类型而不是java.util.Calendar类型。用于排除Trigger日程表中的特定时间范围,比如原本每天执行的任务,排除非工作
1.常见的两种Trigger为SimpleTrigger和CronTrigger.
Cron表达式由7部分组成,分别是秒 分 时 日期 月份 星期 年(可选),空格间隔。
2.Trigger的几种状态及其转化
-
STATE_WAITING(默认): 等待触发
-
STATE_ACQUIRED:获取
-
STATE_COMPLETE:完成
-
STATE_PAUSED:暂停
-
STATE_BLOCKED:阻塞
-
STATE_PAUSED_BLOCKED:从阻塞中暂停
-
STATE_ERROR
-
参考:https://juejin.im/post/6844904053919449096#heading-2
https://www.jianshu.com/p/f2e78c1d8678
https://blog.csdn.net/GAMEloft9/article/details/89454747