Quartz中核心的元素有scheduler, trigger和job, 其中 trigger 和 job 是任务调度的元数据, scheduler 是实际执行调度的控制器;
本文将首先介绍Job,Trigger相关类及持久化的数据库表, 再通过源码分析创建任务时job和trigger的持久化相关内容。
1.Job介绍
Job:是一个接口,只有一个方法void execute(JobExecutionContext context),开发者实现该接口定义运行任务,JobExecutionContext类提供了调度上下文的各种信息。Job运行时的信息保存在JobDataMap实例中。
JobDetail:用来描述Job实现类及其它相关的静态信息,如Job名字、是否禁止并发(IS_NONCONCURRENT)、是否持久化(IS_DURABLE),是否失败转移(REQUESTS_RECOVERY)等信息。
Job持久化只涉及到一张表:qrtz_job_details 比较简单
2.Trigger介绍
4种类型Trigger
CronTrigger,通过Cron表达式,结合Calendar可以支持各种复杂的调度策略;
SimpleTrigger,指定从某一个时间开始,以一定的时间间隔(单位是毫秒)执行一定次数任务,可以方便的支持到秒、分、时。
CalendarIntervalTrigger,指定从某一个时间开始,以一定的时间间隔执行一定次数任务,支持的间隔单位更全,有秒,分钟,小时,天,月,年,星期
DailyTimeIntervalTrigger,指定每天的某个时间段内,以一定的时间间隔执行任务。并且它可以支持指定星期。它适合的任务类似于:指定每天9:00 至 18:00 ,每隔30秒执行一次
6张Trigger相关表
先介绍5张表,4张表是用于存储trigger具体实例信息,1张表是存储trigger的通用信息,(假设表名前缀是qrtz_)
1. qrtz_cron_triggers,存储CronTrigger基本信息
2. qrtz_simple_triggers,存储SimpleTrigger基本信息
3. qrtz_simprop_triggers,存储DailyTimeIntervalTrigger、CalendarIntervalTrigger基本信息
4. qrtz_blob_triggers, 除了上面介绍的四种Trigger,自定义Trigger 默认会使用此表来存储自定义Trigger的信息
5. qrtz_triggers,存储通用trigger信息;包括上次触发时间、下次触发时间,状态等; 所有类型的Trigger在持久化时都会保存一份通用数据到此表;获取任务、执行任务等都是通过此表来操作的
再介绍另1张表名字中也包含trigger的表,用途与上述5张表完全不一样
6. fired_triggers, 存储所有类型Trigger即将触发或正在触发时所产生的运行时信息
3.Trigger持久化源码分析
Quartz简单实例
先看一个简单的Quartz实例, 例子中定义了相应的Job类,包装Job的JobDetail, 调度策略的Trigger, 例子中使用的是CronTrigger, 然后由scheduler进行调度
public static void main(String[] args) throws Throwable {
SchedulerFactory factory = new StdSchedulerFactory();
// 从工厂里面拿到一个scheduler实例
Scheduler scheduler = factory.getScheduler();
// 真正执行的任务并不是Job接口的实例,而是用反射的方式实例化的一个JobDetail实例
JobDetail job = JobBuilder.newJob(MyJob.class).withIdentity("job1", "group1").build();
// 定义一个触发器,startAt方法定义了任务应当开始的时间
CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule("0/10 * * * * ?").withMisfireHandlingInstructionDoNothing();
Trigger trigger = newTrigger().withIdentity("trigger1", "group1").withSchedule(cronScheduleBuilder).build();
// 将任务和Trigger放入scheduler
scheduler.scheduleJob(job, trigger);
scheduler.start();
}复制代码
本文要讲的内容是Quartz是持久化配置时的Trigger是如何持久化的,即项目的 resources/quartz.properties 有类似如下定义:
org.quartz.threadPool.threadCount: 10
org.quartz.threadPool.threadPriority: 5
# for cluster
org.quartz.jobStore.tablePrefix: qrtz_
org.quartz.jobStore.class: org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.isClustered: true
org.quartz.jobStore.dataSource:qzDS
#============================================================================
# Configure Datasources
#============================================================================
#JDBC驱动
org.quartz.dataSource.qzDS.driver:com.mysql.jdbc.Driver
org.quartz.dataSource.qzDS.URL:jdbc:mysql://localhost:3306/quartz
org.quartz.dataSource.qzDS.user:root
org.quartz.dataSource.qzDS.password:
org.quartz.dataSource.qzDS.maxConnection:10
复制代码
QuartzScheduler#scheduleJob
调用Scheduler#scheduleJob()时,Quartz都会将JobDetail和Trigger的信息保存到数据库中,我们来一步一步分析一下trigger的持久化部分的源码实现
scheduler#scheduleJob(job, trigger); Quartz的scheduler最终都委托给了QuartzScheduler, 代码如下
// 先看 Scheduler 中的代码
public Date scheduleJob(JobDetail jobDetail, Trigger trigger)
throws SchedulerException {
// Quartz的scheduler最终都委托给了QuartzScheduler
return sched.scheduleJob(jobDetail, trigger);
}
// 再看 QuartzScheduler 中的代码
public Date scheduleJob(JobDetail jobDetail,
Trigger trigger) throws SchedulerException {
validateState();
// 此处省略了一部分代码。。。。
// name, group, jobName, jobGroup的非空检查 等等
trig.validate();
Calendar cal = null;
if (trigger.getCalendarName() != null) {
cal = resources.getJobStore().retrieveCalendar(trigger.getCalendarName());
}
// 计算出trig的下一次触发时间nextFireTime, 因为是新增的所以也可以说是第一次的触发时间
Date ft = trig.computeFirstFireTime(cal);
if (ft == null) {
throw new SchedulerException(
"Based on configured schedule, the given trigger '" + trigger.getKey() + "' will never fire.");
}
// 开始持久化,即调用JobStoreSupport#storeJobAndTrigger, 下个段落将展开分析
// 抽象类JobStoreSupport实现了Quartz任务持久化接口JobStore,实现了持久化的主要逻辑
// JobStoreSupport的实现子类主要有JobStoreTX, JobStoreCMT
// 这些子类基本上都是对获得Connection进行了封装,而没有去覆盖实现持久化任务的逻辑
resources.getJobStore().storeJobAndTrigger(jobDetail, trig);
// 发送事件通知,可以自定义SchedulerListener来接收处理jobAdded
notifySchedulerListenersJobAdded(jobDetail);
// 向QuartzSchedulerThread发送通知,更新signaled为true,通知 QuartzSchedulerThread重新获取待执行的任务列表
notifySchedulerThread(trigger.getNextFireTime().getTime());
// 发送事件通知,可以自定义SchedulerListener来接收处理jobScheduled
notifySchedulerListenersSchduled(trigger);
return ft;
}复制代码
JobStoreSupport#storeJobAndTrigger
// 本方法开始对Job和Trigger进行持久化
public void storeJobAndTrigger(final JobDetail newJob,
final OperableTrigger newTrigger)
throws JobPersistenceException {
// 加锁处理 (默认配置)
executeInLock(
(isLockOnInsert()) ? LOCK_TRIGGER_ACCESS : null,
new VoidTransactionCallback() {
public void executeVoid(Connection conn) throws JobPersistenceException {
// 保存Job, 比较简单,本文不具体展开了
storeJob(conn, newJob, false);
// 保存Trigger, 调用加锁后事务回调的具体方法
storeTrigger(conn, newTrigger, newJob, false,
Constants.STATE_WAITING, false, false);
}
});
}
// 被回调的具体干活的方法
protected void storeTrigger(Connection conn,
OperableTrigger newTrigger, JobDetail job, boolean replaceExisting, String state,
boolean forceState, boolean recovering)
throws JobPersistenceException {
// storeTrigger方法会被多个地方调用,但在保存Trigger的这个场景下existingTrigger肯定是false
boolean existingTrigger = triggerExists(conn, newTrigger.getKey());
if ((existingTrigger) && (!replaceExisting)) {
throw new ObjectAlreadyExistsException(newTrigger);
}
try {
boolean shouldBepaused;
// 此处省略了一部分代码。。。。
if (job.isConcurrentExectionDisallowed() && !recovering) {
state = checkBlockedState(conn, job.getKey(), state);
}
if (existingTrigger) {
getDelegate().updateTrigger(conn, newTrigger, state, job);
} else {
// getDelegate()获得所委托的具体实现类即StdJDBCDelegate,见下个段落的分析
getDelegate().insertTrigger(conn, newTrigger, state, job);
}
} catch (Exception e) {
throw new JobPersistenceException("Couldn't store trigger '" + newTrigger.getKey() + "' for '"
+ newTrigger.getJobKey() + "' job:" + e.getMessage(), e);
}
}
复制代码
// JobStoreSupport#getDelegate 获得任务持久化的委托执行类, 默认返回 StdJDBCDelegate
protected DriverDelegate getDelegate() throws NoSuchDelegateException {
synchronized(this) {
// 第一次访问getDelegate方法时delegate肯定为null
if(null == delegate) {
try {
if(delegateClassName != null) {
// quartz.properties文件指定了delegateClassName则加载相应的类,默认为null
delegateClass = getClassLoadHelper().loadClass(delegateClassName, DriverDelegate.class);
}
// JobStoreSupport定义了默认的delegateClass
// protected Class<? extends DriverDelegate> delegateClass = StdJDBCDelegate.class;
// 加载DriverDelegate具体实例
delegate = delegateClass.newInstance();
// StdJDBCDelegate 初始化
delegate.initialize(getLog(), tablePrefix, instanceName, instanceId, getClassLoadHelper(), canUseProperties(), getDriverDelegateInitString());
} catch (InstantiationException e) {
// 各种catch, 代码省略...
}
}
return delegate;复制代码
StdJDBCDelegate初始化及TriggerPersistenceDelegate列表的初始化
// StdJDBCDelegate#initialize
public void initialize(Logger logger, String tablePrefix, String schedName, String instanceId, ClassLoadHelper classLoadHelper, boolean useProperties, String initString) throws NoSuchDelegateException {
this.logger = logger;
this.tablePrefix = tablePrefix;
this.schedName = schedName;
this.instanceId = instanceId;
this.useProperties = useProperties;
this.classLoadHelper = classLoadHelper;
// 添加默认的持久化委托类, 此方法很重要,见下文分析
addDefaultTriggerPersistenceDelegates();
if(initString == null)
return;
// 默认此时已返回, 如果有自定义的持久化委托,则继续解析initString, 添加到具体的委托类列表中
String[] settings = initString.split("\\|");
for(String setting: settings) {
String[] parts = setting.split("=");
String name = parts[0];
if(parts.length == 1 || parts[1] == null || parts[1].equals(""))
continue;
if(name.equals("triggerPersistenceDelegateClasses")) {
String[] trigDelegates = parts[1].split(",");
for(String trigDelClassName: trigDelegates) {
try {
Class<?> trigDelClass = classLoadHelper.loadClass(trigDelClassName);
addTriggerPersistenceDelegate((TriggerPersistenceDelegate) trigDelClass.newInstance());
} catch (Exception e) {
throw new NoSuchDelegateException("Error instantiating TriggerPersistenceDelegate of type: " + trigDelClassName, e);
}
}
}
else
throw new NoSuchDelegateException("Unknown setting: '" + name + "'");
}
}
// 添加默认的4种Trigger的持久化委托类,所有的委托类都要实现接口方法 canHandleTriggerType
// canHandleTriggerType 方法是确定Trigger由哪一个委托来执行的关键
protected void addDefaultTriggerPersistenceDelegates() {
addTriggerPersistenceDelegate(new SimpleTriggerPersistenceDelegate());
addTriggerPersistenceDelegate(new CronTriggerPersistenceDelegate());
addTriggerPersistenceDelegate(new CalendarIntervalTriggerPersistenceDelegate());
addTriggerPersistenceDelegate(new DailyTimeIntervalTriggerPersistenceDelegate());
}
复制代码
StdJDBCDelegate#insertTrigger
public int insertTrigger(Connection conn, OperableTrigger trigger, String state,
JobDetail jobDetail) throws SQLException, IOException {
ByteArrayOutputStream baos = null;
if(trigger.getJobDataMap().size() > 0) {
baos = serializeJobData(trigger.getJobDataMap());
}
PreparedStatement ps = null;
int insertResult = 0;
try {
// 创建statement保存qrtz_TRIGGERS
ps = conn.prepareStatement(rtp(INSERT_TRIGGER));
ps.setString(1, trigger.getKey().getName());
ps.setString(2, trigger.getKey().getGroup());
ps.setString(3, trigger.getJobKey().getName());
ps.setString(4, trigger.getJobKey().getGroup());
ps.setString(5, trigger.getDescription());
if(trigger.getNextFireTime() != null)
ps.setBigDecimal(6, new BigDecimal(String.valueOf(trigger
.getNextFireTime().getTime())));
else
ps.setBigDecimal(6, null);
long prevFireTime = -1;
if (trigger.getPreviousFireTime() != null) {
prevFireTime = trigger.getPreviousFireTime().getTime();
}
ps.setBigDecimal(7, new BigDecimal(String.valueOf(prevFireTime)));
ps.setString(8, state);
// 这一句是重点查找当前Trigger的持久化委托类, 委托类中的委托,设计得有点复杂, 在下面段落会有分析
TriggerPersistenceDelegate tDel = findTriggerPersistenceDelegate(trigger);
String type = TTYPE_BLOB;
if(tDel != null)
type = tDel.getHandledTriggerTypeDiscriminator();
ps.setString(9, type);
// 此处省略了一部分代码。。。。
// 执行保存操作,qrtz_TRIGGERS
insertResult = ps.executeUpdate();
if(tDel == null)
// 不是那4种默认Trigger类则执行
insertBlobTrigger(conn, trigger);
else
// 4种Trigger都有各自的实现类
// CronTriggerPersistenceDelegate 操作的表是qrtz_CRON_TRIGGERS
// SimpleTriggerPersistenceDelegate 操作的表是qrtz_SIMPLE_TRIGGERS
// DailyTimeIntervalTriggerPersistenceDelegate和CalendarIntervalTriggerPersistenceDelegate
// 最后两个实现类都是继承了SimplePropertiesTriggerPersistenceDelegateSupport
// 最后两个实现类操作的表都是qrtz_SIMPROP_TRIGGERS
tDel.insertExtendedTriggerProperties(conn, trigger, state, jobDetail);
} finally {
closeStatement(ps);
}
return insertResult;
}
复制代码
扩展:
Quartz的顶层设计各功能模块都可以扩展自定义的,拿本文来说可以扩展实现自己的Trigger, 也可以扩展Trigger的存储方式;
可以自定义一个Trigger,继承上面的4种Trigger, 也可以自定义该Trigger相应的存储TriggerPersistenceDelegate;
自定义的Trigger存储器怎么才能生效呢?
当然是修改quartz.properties文件,增加一行配置,例:
org.quartz.jobStore.driverDelegateInitString: triggerPersistenceDelegateClasses=com.shengu.quartz.trigger.MyPersistenceDelegateClasses复制代码
也可以支持定义多个Trigger存储器,通过上面的源码可以看出来,多个的定义是通过 | 来分隔的;
quartz的各种扩展一般都是通过在quartz.properties中增加配置来实现的
小结:
创建任务时,Job操作一张表 qrtz_job_detail;Trigger会操作两张表
1.保存Trigger通用信息到qrtz_triggers
2.保存Trigger具体实例信息到下面对应的一个表中
2.1 系统默认的4种Trigger分别对应 qrtz_cron_triggers,qrtz_simple_triggers, qrtz_simprop_triggers,qrtz_simprop_triggers
2.2 自定义Trigger默认对应保存到qrtz_blob_triggers
Trigger存储是支持扩展的,可以自定义Trigger存储在哪张表中