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由两部分组成:namegroup,这个二元组全局唯一,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,默认常用的是 SimpleTriggerCronTrigger

同样的 Trigger也有唯一的 key,同样也是由 namegroup组成

因为一个 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

七、其他配置和一些问题解决

W3C文档

问题排查方向

posted @ 2022-03-24 10:22  小么VinVin  阅读(235)  评论(0编辑  收藏  举报