Quartz快速上手
Quartz快速上手
Quartz是 Java的一个定时任务规范,本身已经做好了实现,同时 Spring既集成了 Quartz又做了一个 SpringTask;
在实际的开发中,二者都可以选用,且基本的核心逻辑相似
一、Quartz概述
我们使用 Quartz进行定时任务调度的时候,存在以下几个核心概念 Scheduler
, Trigger
, Job
- Scheduler:进行实际的任务调度
- Trigger:代表任务的触发条件
- Job:代表抽象的任务逻辑
- JobDetail:代表具体参与调度的任务体
我们在创建定时任务并赋予触发条件使其生效的时候,往往需要进行诸多配置,如:持久化、组件绑定等等。因此还会用到许多相应的 Builder类来帮助完成实例化及注册的操作。
1.1 代码示例
Job任务的描述:
public class ExampleJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
/*
以下主要是展示了任务执行过程中,某些值的获取操作;
也可以执行具体的业务逻辑等
总之,就是一定要实现 Job接口,并且此时这个 Job只是被描述了而已,还不会立刻参与调度
*/
Object tv1 = context.getTrigger().getJobDataMap().get("t1");
Object tv2 = context.getTrigger().getJobDataMap().get("t2");
// 获取 JobDetail的 JobDataMap中事先定义的数据
Object jv1 = context.getJobDetail().getJobDataMap().get("j1");
Object jv2 = context.getJobDetail().getJobDataMap().get("j2");
// 获取该任务的 key,内有 name 和 group等信息
JobKey key = context.getJobDetail().getKey();
System.out.println(key.getName() + " " + key.getGroup());
// 是 trigger和 jobDetail中两个 jobDataMap数据的并集,但如果存在相同的 key,则 trigger的会覆盖 jobDetail的
JobDataMap mergedJobDataMap = context.getMergedJobDataMap();
Object sv = null;
try {
sv = context.getScheduler().getContext().get("skey");
} catch (SchedulerException e) {
e.printStackTrace();
}
}
}
定时任务的申明
/**
* 当该方法被执行后,就会创建一个定时任务,依据创建时声明的逻辑进行执行
*/
private void scheduleSimpleTask() {
try {
// 创建一个 Scheduler作为调度中心
Scheduler defaultScheduler = StdSchedulerFactory.getDefaultScheduler();
defaultScheduler.getContext().put("skey", "svalue");
// 创建一个 Trigger,定义调度计划
Trigger trigger = TriggerBuilder
.newTrigger()
.withIdentity("trigger1", "group1")
.usingJobData("t1", "v1")
.startAt(DateBuilder.newDate().build())
.endAt(DateBuilder.evenHourDateAfterNow())
.withPriority(1)
.withSchedule(SimpleScheduleBuilder
.simpleSchedule()
.withIntervalInSeconds(3)
.withMisfireHandlingInstructionIgnoreMisfires()
.repeatForever())
.build();
// 创建一个 JobDetail,它是我们之前描述的 Job具体参与调度的逻辑类,同时可以传递数据
JobDetail job = JobBuilder
.newJob(ExampleJob.class)
.usingJobData("j1", "jv1")
.withIdentity("myjob", "mygroup")
.storeDurably()
.requestRecovery()
.build();
job.getJobDataMap().put("j2", "jv2");
// 注册 job和 trigger,并启动 scheduler
defaultScheduler.scheduleJob(job, trigger);
defaultScheduler.start();
} catch (SchedulerException e) {
e.printStackTrace();
}
}
当 JobDetail的一个 Trigger被触发之后,该 Job对应的 execute()
方法就会被自动调用。
JobDetail对象是在将 Job加入到 Scheduler的时候,由系统自动调用创造的,包含了该 Job的所有内容和在创建对象时就声明了的一些键值对数据
二、Job和 JobDetail
2.1 Job
Job很容易定义,只要自己声明一个类实现 Job接口,重写 execute()
方法即可。
但是这个类仅仅描述了该任务要完成的内容,并没有说执行的时间和其他可能需要的数据内容,而 Quartz在进行任务调度时绝对需要其他的参数,因此实际参与定时任务调度的是 JobDetail
类
public class MyJob implements Job {
@Override
public void execute(JobExecutionContext context) {
// do something...
}
}
所以我们完全可以只创建 1个 Job,但是注册多个基于它的 JobDetail类,每一个 JobDetail都有各自独立的 JobDetailMap和属性集
2.2 JobDetail
JobDetail是在进行任务调度时,实际参与进来的对象,我们一般会在创建调度任务的时候通过 JobBuilder
进行实例化
每个 JobDetail都会有唯一的 key值,一个 Key由两部分组成:name
和 group
,这个二元组全局唯一,group有默认值,也可以自己设置
public void createTimerTask() {
// create scheduler
// create trigger
// create jobdetial
JobDetail jd = JobBuilder
.newJob(MyJob.class) // 将 Job和 JobDetail绑定
.withIdentity("name", "group") // 设置唯一的 key
.usingJobData("k1", "v1")
.build();
// bind trgger and jobdetail on the scheduler
scheduler.scheduleJob(job, trigger);
scheduler.start();
}
一个 JobDetail只对应一个 Job,但是一个 Job却可以有多个 JobDetail任务。
每次 Trigger触发后,Scheduler都会调用 execute()
方法都会创建该 Job的一个实例,任务执行完毕之后,又会丢弃该引用,那么这个实例就会被垃圾回收。
所以,在该 Job类中,必须要有一个无参构造(默认的 JobFactory通过 Job的无参构造来创建 Job对象),并且在 Job中定义的状态变量都将失效(像全局变量 i++这种操作,没有太大的意义)
因此,在某些 Job需要外部变量值,或者需要某些跟任务执行次数有关的值时,就得通过 JobDataMap
来实现
2.3 JobDataMap
JobDataMap就是一个 Java.util.map的子类,可用来存储不限量的数据对象(实际会进行序列化存储),它是和 JobDetail绑定的;也就是说,该 JobDetail的多次触发生成的多个 Job都可以获取到相同的值,甚至通过 JobDataMap实现数据通信(当然,并发会出现不一致的问题)
在创建 JobDetail的时候,我们可以直接将数据存到 JobDataMap中;也可以在创建完毕后,在绑定之前手动获取 map后存入数据
public void initJobDetail() {
// 方式一
JobDetail jd = JobBuilder
.newJob(MyJob.class)
.usingJobData("k1", "v1")
.build();
// 方式二
jd.getJobDataMap().put("k2", "v2");
}
在 Job执行的过程中,我们可以获取到 JobDataMap然后得到其中存储的键值对
public class MyJob {
public MyJob() {
}
@Override
public void execute(JobExecuteContext context) {
JobDataMap map = context.getJobDetail().getJobDataMap();
String v1 = map.getAsString("k1");
System.out.println(v1);
}
}
如果我们的 JobDetail被设置成了持久化存储的,那么存到 JobDataMap中的数据也会被序列化,因此可能会存在数据版本不一致的问题(当前对象已被序列化,然后这个类被更改了,当反序列化的时候就可能会出现问题)。
其实,在 Trigger中也有 JobDataMap可以存储数据,使用 execute()
方法的入参,我们可以调用 context.getMergedJobDataMap()
方法,获取 Job和 Trigger二者 JobDataMap的并集,如果有相同的 Key,那么 Trigger中的会覆盖 Job中的内容。
并且如果我们非常确定 JobDataMap中会存有某些 Key且会在 Job中用到,我们也可以提前声明,实现一个类似 DI的效果;但是必须要为这些 Key实现 getter方法
@Getter
public class MyJob {
String k1;
Flout k2;
ArrayList<String> k3;
public MyJob() {
}
@Override
public void execute(JobExecuteContext context) {
JobDataMap map = context.getJobDetail().getJobDataMap();
System.out.println(k1);
System.out.println(k2);
System.out.println(k3);
}
}
2.4 Job的状态、并发、持久、恢复
@DisallowConcurrentExecution
- 当一个 JobDetail关联了多个 Trigger后,且多个 Trigger同时触发的时候,禁止并发地执行一个 JobDetail的多个实例。该注解作用在定义 Job类的时候。
PersisJobDataAfterExecution
- 当成功地执行了 Job类的 execute()
方法后,更新 JobDetail中的 JobDataMap内的数据而不是更新旧的数据。该注解同样也是作用在定义 Job类的时候。一般两个注解会一起使用。
Durability
属性 - 可以设置 JobDetail对象的持久化属性,如果一个 Job是非持久的,那么当没有活跃的 Trigger与之关联时,会被自动地从 Scheduler中删除,也就是说非持久的 Job的生命周期是由 Trigger的存在与否决定的。
RequestRecovery
属性 - 当一个 JobDetail被触发执行的时候,Scheduler突然崩溃了,那么当该 Scheduler重新启动后,设置为RequestRecovery
的 Job会被重新执行
三、Trigger
Trigger代表 Quartz中的任务触发器,它会跟任务进行绑定,之后当达到触发器的条件后,会自动触发执行绑定的任务。
Quartz可以自定义 Trigger,默认常用的是 SimpleTrigger
和 CronTrigger
同样的 Trigger也有唯一的 key,同样也是由 name
和 group
组成
因为一个 JobDetail可以绑定多个 Trigger,因此多个 Trigger之间应该有一个优先级(比如:Trig1定为周一,Trig2定位1号,如果恰好二者撞到一起了,就应该有一个优先级),但是这个优先级仅针对同时出发的多个 Trigger之间,单凡时间上有先后,就还是会依照时间顺序
如果一个 Trigger因为特殊原因导致 错过触发(miss fire),各个 Trigger会采取相应的措施进行修正,,默认为 “智能机制”,即根据 Trigger的类型、配置等动态调整其行为。当 Scheduler启动的时候,会查询所有错过触发的持久的 Trigger,并根据其各自的机制更新 Trigger的信息
3.1 SimpleTrigger
使用 SimpleTrigger可以简单的指定一项任务的开始时间、结束时间、重复次数、重复间隔等
TriggerBuilder会为那些没有显式声明的值提供合理的默认值
// 创建一个 Trigger,定义调度计划
Trigger trigger = TriggerBuilder
.newTrigger()
.withIdentity("trigger1", "group1") // 指定当前 Trigger的 Key
.usingJobData("t1", "v1") // 将数据存入 Trigger的 JobDataMap中
.startAt(DateBuilder.newDate().build()) // 指定开始时间,也可以传入 FutureDate
.endAt(DateBuilder.evenHourDateAfterNow()) // 指定结束时间
.withPriority(1) // 指定优先级
// 采用 SimpleScheduleBuilder构造简单的调度方式
// 也就是说,之后的 build()会返回一个 SimpleTrigger
.withSchedule(SimpleScheduleBuilder
.simpleSchedule()
.withIntervalInSeconds(3) // 指定间隔时间
.withMisfireHandlingInstructionIgnoreMisfires() // 指定错过出发后的处理逻辑
.repeatForever())
.build();
SimpleTrigger有以下几个 MisFire的策略:
- MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY
- MISFIRE_INSTRUCTION_FIRE_NOW
- MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT
- MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT
- MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT
- MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT
当然所有的 Trigger都可以使用 SMART_POLICY,Trigger实例会根据状态自动选用合适的策略来执行
3.2 CronTrigger
CronTrigger通常可以比 SimpleTrigger更加有用,它可以采取一种更加精确的方式实现任务时间的选定。
它采用 Cron表达式确定 Trigger的时间,一般由七个部分组成,各部分之间用空格分隔:
- Second
- Minute
- Hour
- Day-of-Month
- Month
- Day-of-Week
- Year(optional)
eg: 0 0 12 ? * WED
表示 每个星期三的中午 12点
具体某一部分可以使用 -
来划分范围,使用 ,
来进行子集的拼接,使用 /
来表示执行的间隔 eg: 0 0/2 6-9,12 ? * MON-WED,SUN
表示周一到周三和周日的6-9点和12点每2分钟执行一次
* 通配符表示可能该字段的每个可能值都符合,? 通配符表示无特定的值,用在日期或星期几上;总之就是在 日和 周这两个字段上,一个用 *
, 另一个用 ?
,二者岔开来
L表示 Last,可用在 日、月、周上,类似 -1表示最后,并且可参与计算,L-3就表示倒数第三,eg: 0 0 0 2L * ?
表示 每月的倒数第
W 表示和指定日期最近的工作日
# 表示第几个,eg: 0 0 0 ? 12 FRI#2
表示 12月的第二哥周五
当我们遇到一些特别复杂的时间调度的时候,可以选择适当的拆分,拆成多个 Cron表达式,创建多个 CronTrigger来进行
// 创建一个 Trigger,定义调度计划
Trigger trigger = TriggerBuilder
.newTrigger()
.withIdentity("trigger1", "group1")
.startAt(DateBuilder.evenHourDateAfterNow()) // 指定 Trigger的开始生效时间
// 表示采用 Cron的方式设置调度时间
.withSchedule(CronScheduleBuilder
.cronSchedule("0 0/2 8-17 * * ?")
.withMisfireHandlingInstructionIgnoreMisfires())
.forJob("myjob", "mygroup")
.build();
CronTrigger的 MisFire策略为:
- MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY
- MISFIRE_INSTRUCTION_DO_NOTHING
- MISFIRE_INSTRUCTION_FIRE_NOW
当然,也可以使用 Smart的方式
四、Listener
Listener监听器可以根据 Scheduler调度器中发生的事件进行对应的操作。包括:SchedulerListener, TriggerListener, JobListener
我们可以通过实现 XxxListener
接口或者继承 XxxListenerSupport
类来自定义监听器,然后监听器在运行时会向调度程序注册,并且给出一个唯一的名称 name。
一般 Listener会和 ListenerManager一起注册,并配有该 Listener监听的 Job或 Trigger的 Matcher。
注意:Listener会在运行时间内与调度程序一起注册,并且不与 Jobs和 Triggers一起存储在 JobStore中。因为 Listener一般看作是与应用程序的集成点,因此每次运行应用程序的时候都需要重新注册该调度程序。
4.1 JobListener
监听 Job的各种状态
public class MyJobListener implements JobListener {
private String name;
public MyJobListener() {
}
public MyJobListener(String name) {
this.name = name;
}
/**
* 获取当前 JobListener的名称
*
* @return
*/
@Override
public String getName() {
return this.name;
}
/**
* 由 Scheduler调用,在 JobDetail任务之前执行(当一个 Trigger触发后)
*
* @param context
*/
@Override
public void jobToBeExecuted(JobExecutionContext context) {
context.getJobDetail().getJobBuilder().usingJobData("aaa", "bbb");
}
/**
* 由 Scheduler调用,当 TriggerListener否决了该任务的执行后调用
* 这个方法一般不执行
* 除非 TriggerListener中的 vetoJobExecution()方法返回 true
* 注意:如果该方法执行了,那么前后两个方法都不会再执行
*
* @param context
*/
@Override
public void jobExecutionVetoed(JobExecutionContext context) {
}
/**
* Job执行完之后调用,如果 JobException不为 null,就说明 Job执行过程中存在异常
*
* @param context
* @param jobException
*/
@Override
public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {
}
}
4.2 TriggerListener
监听 Trigger的各种状态
public class MyTriggerListener implements TriggerListener {
private String name;
public MyTriggerListener(String name) {
this.name = name;
}
public MyTriggerListener() {
}
/**
* 获取该 Trigger的唯一名称
*
* @return
*/
@Override
public String getName() {
return name;
}
/**
* 触发器成功触发,与它关联的 JobDetail即将被执行前调用
*
* @param trigger
* @param context
*/
@Override
public void triggerFired(Trigger trigger, JobExecutionContext context) {
}
/**
* 会在 triggerFired()方法后调用,如果当前方法返回 false,那么 JobDetail不会执行,
* 且会同时触发 JobListener中的 JobExecutionVetoed()函数
*
* @param trigger
* @param context
* @return
*/
@Override
public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) {
return false;
}
/**
* 触发器错过触发后调用
* 要注意该方法的执行效率,可能会有大量的触发器都一起失败的场景
*
* @param trigger
*/
@Override
public void triggerMisfired(Trigger trigger) {
}
/**
* 任务完成后调用
*
* @param trigger
* @param context
* @param triggerInstructionCode
*/
@Override
public void triggerComplete(Trigger trigger, JobExecutionContext context, Trigger.CompletedExecutionInstruction triggerInstructionCode) {
}
}
4.3 SchedulerListener
SchedulerListener和 JobListener与 TriggerListener差不多,只是监听的行动不同而已
public class MyScheduleListener implements SchedulerListener {
/**
* JobDetail任务被成功调用了
*
* @param trigger
*/
@Override
public void jobScheduled(Trigger trigger) {
}
/**
* JobDetail任务没有被成功调用
*
* @param triggerKey
*/
@Override
public void jobUnscheduled(TriggerKey triggerKey) {
}
/**
* 触发器不会再被触发后调用
*
* @param trigger
*/
@Override
public void triggerFinalized(Trigger trigger) {
}
/**
* 触发器被暂停了,接收 triggerKey
*
* @param triggerKey
*/
@Override
public void triggerPaused(TriggerKey triggerKey) {
}
/**
* 触发器被暂停了,接收 trigger组
*
* @param triggerGroup
*/
@Override
public void triggersPaused(String triggerGroup) {
}
/**
* 触发器由暂停状态恢复了,接收 triggerKey
*
* @param triggerKey
*/
@Override
public void triggerResumed(TriggerKey triggerKey) {
}
/**
* 触发器由暂停状态恢复了,接收 trigger组
*
* @param triggerGroup
*/
@Override
public void triggersResumed(String triggerGroup) {
}
/**
* JobDetail任务添加
*
* @param jobDetail
*/
@Override
public void jobAdded(JobDetail jobDetail) {
}
/**
* JobDetail任务删除
*
* @param jobKey
*/
@Override
public void jobDeleted(JobKey jobKey) {
}
/**
* 单个 JobDetail任务暂停
*
* @param jobKey
*/
@Override
public void jobPaused(JobKey jobKey) {
}
/**
* 一个 JobDetail组暂停
*
* @param jobGroup
*/
@Override
public void jobsPaused(String jobGroup) {
}
/**
* 单个 JobDetail恢复
*
* @param jobKey
*/
@Override
public void jobResumed(JobKey jobKey) {
}
/**
* 一个 JobDetail组恢复
*
* @param jobGroup
*/
@Override
public void jobsResumed(String jobGroup) {
}
/**
* 调度异常
*
* @param msg
* @param cause
*/
@Override
public void schedulerError(String msg, SchedulerException cause) {
}
/**
* 当调度器已 Standby的模式进行调度后
*/
@Override
public void schedulerInStandbyMode() {
}
/**
* 调度器成功开启
*/
@Override
public void schedulerStarted() {
}
/**
* 调度器开启
*/
@Override
public void schedulerStarting() {
}
/**
* 调度器成功关闭
*/
@Override
public void schedulerShutdown() {
}
/**
* 调度器开始关闭
*/
@Override
public void schedulerShuttingdown() {
}
/**
* 调度器的数据清除
*/
@Override
public void schedulingDataCleared() {
}
}
4.4 监听器的注册
以上都只是通过继承或实现描述了我们期望的监听器的功能而已,真正希望监听器生效的话,还需要把监听器添加到调度器中。
所有的监听器都要通过 ListenerManager对象注册,注册时可以指定监听的规则,如:监听某一个组,监听某一个 key,监听某几个符合某种 Pattern的组等等
// 创建一个 Scheduler
Scheduler defaultScheduler = StdSchedulerFactory.getDefaultScheduler();
defaultScheduler.getContext().put("skey", "svalue");
// 为该调度器添加监听器,监听指定的 JobKey,也可以指定 Job组
defaultScheduler
.getListenerManager()
.addJobListener(new MyJobListener(),
KeyMatcher.keyEquals(JobKey.jobKey("j1", "jv1")));
defaultScheduler
.getListenerManager()
.addJobListener(new MyJobListener(),
EverythingMatcher.allJobs());
defaultScheduler
.getListenerManager()
.addJobListener(new MyJobListener(),
AndMatcher.and(GroupMatcher.jobGroupContains("a"), GroupMatcher.jobGroupContains("b")));
defaultScheduler
.getListenerManager()
.addJobListener(new MyJobListener(),
AndMatcher.and(KeyMatcher.keyEquals(JobKey.jobKey("a", "b")), GroupMatcher.jobGroupContains("b")));
defaultScheduler.getListenerManager().addSchedulerListener(new MyScheduleListener());
五、JobStore
Quartz会将定时任务相关的数据存储在 JobStore中,默认有三种 JobStore的格式:RAMJobStore, JDBCJobStore, TerracottaJobStore。
5.1 RAMJobStore
RAMJobStore速度最快,使用简单。它会直接将所有调度信息都存到内存中,进行调度。因此,一旦程序重启、服务器宕机,原有任务都会清空。
并且因为是基于内存的,所以集群间并不同步,只能在单台服务器上使用。
Quartz默认就是 RAMJobStore。
内部本质上是一些 HashMap, TreeSet, HashSet
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;
5.2 JDBCJobStore
JDBCStore有两种类型:JobStoreTX 和 JobStoreCMT。
JobStoreCMT将事务依赖于使用 Quartz的应用程序,也就是说 Quartz本身并不实现具体的事物处理逻辑,而是使用上层的框架。
这会使得任务调度的工作成为全局事务中的一部分,也就是说 JobStoreCMT会使用两部分的数据源,一个是由应用服务器全局管理的,另一个是自己的。
也就是说,不管是 Quartz还是业务代码报错,都会进行回滚
JobStoreTX则与 JobStoreCMT相反,它自己管理事务的提交回滚。如果 Quartz中的 Job出现问题,只会回滚它自己,外部的可能的业务代码不会回滚
5.2.1 JobStoreCMT
CMT 即 Container Managed Transaction
需要的配置:
# 配置 JobStore类型
org.quartz.jobStore.class= org.quartz.impl.jdbcjobstore.JobStoreCMT
# 配置驱动代理
org.quartz.jobStore.driverDelegateClass= org.quartz.impl.jdbcjobstore.StdJDBCDelegate
# 配置两个数据源
# 第一个:配置不受应用容器管理的数据源
org.quartz.jobStore.nonManagedTXDataSource = qzDS
org.quartz.dataSource.qzDS.driver = com.mysql.jdbc.Driver
org.quartz.dataSource.qzDS.URL = jdbc:mysql://localhost:3306/testDB
org.quartz.dataSource.qzDS.user = root
org.quartz.dataSource.qzDS.password = admin
org.quartz.dataSource.qzDS.maxConnections = 10
# 第二个:配置受应用容器管理的数据源
org.quartz.dataSource.dataSource=myDS
org.quartz.dataSource.jndiURL = jdbc/mysql
org.quartz.dataSource.myDS.jndiAlwaysLookup = DB_JNDI_ALWAYS_LOOKUP
org.quartz.dataSource.myDS.java.naming.factory.initial = org.apache.naming.java.javaURLContextFactory
org.quartz.dataSource.myDS.java.naming.provider.url = http://localhost:8080
org.quartz.dataSource.myDS.java.naming.security.principal = root
org.quartz.dataSource.myDS.java.naming.security.credentials = admin
5.2.2 JobStoreTX
需要的配置
org.quartz.jobStore.class= org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.dataSource=qzDS
org.quartz.dataSource.qzDS.driver= com.mysql.jdbc.Driver
org.quartz.dataSource.qzDS.URL= jdbc:mysql://localhost:3306/testDB
org.quartz.dataSource.qzDS.user= root
org.quartz.dataSource.qzDS.password= admin
org.quartz.dataSource.qzDS.maxConnection= 20
5.3 TerracottaJobStore
Terracotta将数据直接存储到了指定的服务器上,而不是数据库。它的性能比 JdbcStore好一个数量级
需要的配置
org.quartz.jobStore.class = org.terracotta.quartz.TerracottaJobStore
org.quartz.jobStore.tcConfigUrl = localhost:9510
六、线程池
Quartz任务调度会使用其自身的线程池,如果线程池设置得太大,就会影响整体的性能;如果线程池设置得太小,那么同时触发的多个任务会抢占线程,没抢到的会阻塞自己等待,如果阻塞的时间太长,就会导致 JobMisFire。
因此,需要根据实际的情况,设置合理大小的线程池。
# 配置线程池的属性
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
# 线程池里的线程数,默认值是10,当执行任务会并发执行多个耗时任务时,要根据业务特点选择线程池的大小。
org.quartz.threadPool.threadCount = 4
org.quartz.threadPool.threadPriority = 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true