Quartz框架详解分析

1 Quartz框架

本例quartz使用版本为

 <dependency>
   <groupId>org.quartz-scheduler</groupId>
    <artifactId>quartz</artifactId>
    <version>2.3.2</version>
</dependency> 

点击了解Quartz框架基本知识

1.1 入门demo

几个概念搞清楚先:
触发器 Trigger: 什么时候工作
任务 Job: 做什么工作
调度器 Scheduler: 搭配 Trigger和Job

Job类

import org.quartz.Job;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

import java.text.SimpleDateFormat;
import java.util.Date;
public class MyJob implements Job {

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        JobDetail jobDetail = context.getJobDetail();
        String email = jobDetail.getJobDataMap().getString("email");

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
        String now = sdf.format(new Date());

        System.out.printf("向邮件 %s,发送了一封定时邮件,时间: %s %n",email,now);
    }
}

调用

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

import java.text.SimpleDateFormat;
import java.util.Date;
public class TestQuartz {

    public static void main(String[] args) throws Exception {
        //创建调度器
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        //创建触发器
        Trigger trigger = TriggerBuilder.newTrigger().withIdentity("tri_001","tri_group_001")//定义名称和所属的组
                // 根据为触发器配置的时间表,将触发器启动的时间设置为当前时刻(触发器此时可能会触发,也可能不会触发)
                .startAt()
                .withSchedule(SimpleScheduleBuilder.simpleSchedule()
                        .withIntervalInSeconds(2) //每隔两秒
                        .withRepeatCount(2) //总共执行3次(第一次执行不基数)
                   ).build();

        //定义一个job
        JobDetail job = JobBuilder.newJob(MyJob.class) //指定干活的类MailJob
                .withIdentity("job_001","job_group_001")//定义任务名称和分组
                .usingJobData("email","test@test.com")//定义属性
                .build();
        //使用jobDateMap
        job.getJobDataMap().put("email","admin@test.com");
        //调度假如这个job
        Date date = scheduler.scheduleJob(job, trigger);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
        System.out.printf("定时器开始时间:%s %n",sdf.format(date));

        //启动
        scheduler.start();
        Thread.sleep(20000);
        scheduler.shutdown(true);
    }
}

1.2 Job 讲解

1.2.1 Job简介

job一般需要实现org.quartz.Job的接口,Job 其实是由 3 个部分组成:

  • JobDetail:用于描述这个Job是做什么的
  • 实现Job的类:具体干活的
  • JobDataMap:给 Job 提供参数用的
    这是除了usingJobData 方式之外,还可以是其他方式,像这样
    job.getJobDataMap().put("email", "test@test.com");

1.2.2 Job 并发

默认的情况下,无论上一次任务是否结束或者完成,只要规定的时间到了,那么下一次就开始。
有时候会做长时间的任务,比如数据库备份,这个时候就希望上一次备份成功结束之后,才开始下一次备份,即便是规定时间到了,也不能开始,因为这样很有可能造成数据库被锁死 (几个线程同时备份数据库,引发无法预计的混乱)。

那么在这种情况下,给数据库备份任务增加一个注解就好了:@DisallowConcurrentExecution

job类

import cn.hutool.core.date.DateUtil;
import lombok.SneakyThrows;
import org.quartz.*;

@DisallowConcurrentExecution
public class DatabaseBackupJob implements Job {

    @SneakyThrows
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        JobDetail detail = context.getJobDetail();
        String database = detail.getJobDataMap().getString("database");
        System.out.printf("给数据库 %s 备份,耗时10s,当前时间 %s %n",database, DateUtil.now());
        Thread.sleep(3000);
    }
}

调用类

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

import java.text.SimpleDateFormat;
import java.util.Date;

public class TestQuartz {

    public static void main(String[] args) throws Exception{
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        //创建触发器
        Trigger trigger = TriggerBuilder.newTrigger().withIdentity("tri_001","tri_group_001")
                // 根据为触发器配置的时间表,将触发器启动的时间设置为当前时刻(触发器此时可能会触发,也可能不会触发)
                .startNow()
                .withSchedule(SimpleScheduleBuilder.simpleSchedule()
                        .withIntervalInSeconds(2) //每隔两秒
                        .withRepeatCount(3) //重复
                ).build();

        //定义一个job
        JobDetail job = JobBuilder.newJob(DatabaseBackupJob.class)
                .withIdentity("job_002","job_group_002")
                .usingJobData("database","mysql")
                .build();

        //调度假如这个job
        Date date = scheduler.scheduleJob(job, trigger);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
        System.out.printf("数据库定时器开始时间:%s %n",sdf.format(date));

        //启动
        scheduler.start();

        Thread.sleep(20000);
        scheduler.shutdown(true);

    }
}

1.2.3 Job 异常

任务里发生异常是很常见的。 异常处理办法通常是两种:

  • 当异常发生,那么就通知所有管理这个 Job 的调度,停止运行它
  • 当异常发生,修改一下参数,马上重新运行

异常一:

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

public class ExceptionJob1 implements Job {
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        int i=0;
        try{
            System.out.println(1/i);
        }catch (Exception e){
            System.out.println("发生了异常,取消了这个job对应的所有的调度");
            JobExecutionException je = new JobExecutionException(e);
            je.setUnscheduleAllTriggers(true);
            throw je;
        }

    }
}

异常二:

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

public class ExceptionJob2 implements Job {
    static int i=0;
    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {

        try{
            System.out.println("运算结果:"+1/i);
        }catch (Exception e){
            System.out.println("发生了异常,修改下参数,立即重新执行");
            i=1;
            JobExecutionException je = new JobExecutionException(e);
            je.setRefireImmediately(true);
            throw je;
        }
    }
}

调度类,只是改下job中的异常类一或者二即可

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

import java.text.SimpleDateFormat;
import java.util.Date;

public class TestQuartz {

    public static void main(String[] args) throws Exception{
        exceptionJob1();
    }

    private static void exceptionJob1() throws Exception{
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        //创建触发器
        Trigger trigger = TriggerBuilder.newTrigger().withIdentity("tri_001","tri_group_001")
              
                .startNow()
                .withSchedule(SimpleScheduleBuilder.simpleSchedule()
                        .withIntervalInSeconds(2) //每隔两秒
                        .withRepeatCount(3) //重复
                ).build();

        //定义一个job
        JobDetail job = JobBuilder.newJob(ExceptionJob1.class)
                .withIdentity("exceptionJob1","exception_group")
                .build();

        //调度假如这个job
        Date date = scheduler.scheduleJob(job, trigger);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
        System.out.printf("数据库定时器开始时间:%s %n",sdf.format(date));

        //启动
        scheduler.start();

        Thread.sleep(20000);
        scheduler.shutdown(true);
    }

1.2.4 Job 中断

业务上,有时候需要中断任务,那么这个Job 需要实现 InterruptableJob 接口而不是 Job 接口,然后就方便中断了

import org.quartz.InterruptableJob;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.UnableToInterruptJobException;

public class StoppableJob implements InterruptableJob {

    private boolean stop = false;
    @Override
    public void interrupt() throws UnableToInterruptJobException {
        System.out.println("被调度叫停");
        stop=true;
    }

    @Override
    public synchronized void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        while (true){
            if(stop)
                break;
            try {
                System.out.println("每隔一秒,进行一次检测,看看是否停止");
                Thread.sleep(1000);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            System.out.println("持续工作中。。。。");
        }
    }
}

调度类

public class TestQuartz {

    public static void main(String[] args) throws Exception{
        stop();

    }

    private static void stop() throws Exception{
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        //创建触发器
        Trigger trigger = TriggerBuilder.newTrigger().withIdentity("tri_001","tri_group_001")
                // 根据为触发器配置的时间表,将触发器启动的时间设置为当前时刻(触发器此时可能会触发,也可能不会触发)
                .startNow()
                .build();

        //定义一个job
        JobDetail job = JobBuilder.newJob(StoppableJob.class)
                .withIdentity("stop_job","stop_job_group")
                .build();

        //调度假如这个job
        Date date = scheduler.scheduleJob(job, trigger);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
        System.out.printf("数据库定时器开始时间:%s %n",sdf.format(date));

        //启动
        scheduler.start();

        Thread.sleep(5000);
        System.out.println("过5秒,调度停止job");
        scheduler.interrupt(job.getKey());



        Thread.sleep(20000);
        scheduler.shutdown(true);
    }
}

1.3 Trigger 触发器

1.3.1 SimpleTrigger

Trigger 就是触发器的意思,用来指定什么时间开始触发,触发多少次,每隔多久触发一次,SimpleTrigger 可以方便的实现一系列的触发机制。

Job类都差不多,重点是调用类差别

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

import java.text.SimpleDateFormat;
import java.util.Date;

public class TestQuartz {

    public static void main(String[] args) throws Exception {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        System.out.println("当前时间是:"+sdf.format(new Date()));
        //下一个8秒的倍数
        Date startTime = DateBuilder.nextGivenSecondDate(null, 8);
        //10 秒后运行
//        startTime = DateBuilder.futureDate(10, DateBuilder.IntervalUnit.SECOND);
        //累计n次,间隔n秒
        System.out.println("延迟后的时间:"+sdf.format(startTime));

        JobDetail job = JobBuilder.newJob(MailJob.class).withIdentity("mailJob", "mailGroup")
                .build();
        SimpleTrigger trigger = (SimpleTrigger) TriggerBuilder.newTrigger()
                .withIdentity("trigger_simple","trigger_simple_group")
//                .startNow()
                .startAt(startTime)
                .withSchedule(SimpleScheduleBuilder.simpleSchedule()
                        .withRepeatCount(2)
  //                      .repeatForever() //永远运行下去
                        .withIntervalInSeconds(1))
                .build();
        Date date = scheduler.scheduleJob(job, trigger);
        System.out.println("当前时间是:"+sdf.format(new Date()));
        System.out.printf("%s 这个任务会在 %s 准时开始运行,累计运行 %d 次,间隔时间是 %d 毫秒 %n",
                job.getKey(),sdf.format(date),trigger.getRepeatCount()+1,trigger.getRepeatInterval());

        scheduler.start();
        Thread.sleep(20000);
        scheduler.shutdown(true);
    }
}

1.3.2 CornTrigger

CronLinux下的一个定时器,功能很强大,但是表达式更为复杂
CronTrigger 就是用 Cron 表达式来安排触发时间和次数的。
点击了解Corn表达式

import cn.quartz.simple.MailJob;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

import java.util.Date;

public class TestQuartz {

    public static void main(String[] args) throws Exception {
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

        Date startTime = DateBuilder.nextGivenSecondDate(null, 8);

        JobDetail job = JobBuilder.newJob(MailJob.class).withIdentity("mailJob", "mailGroup").build();

        CronTrigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("trigger1", "group1")
                .withSchedule(CronScheduleBuilder.cronSchedule("0/2 * * * * ?"))
                .build();

        // schedule it to run!
        scheduler.scheduleJob(job, trigger);

        System.out.println("使用的Cron表达式是:"+trigger.getCronExpression());
//            System.out.printf("%s 这个任务会在 %s 准时开始运行,累计运行%d次,间隔时间是%d毫秒%n", job.getKey(), ft.toLocaleString(), trigger.getRepeatCount()+1, trigger.getRepeatInterval());

        scheduler.start();

        //等待20秒,让前面的任务都执行完了之后,再关闭调度器
        Thread.sleep(10000);
        scheduler.shutdown(true);
    }
}

1.4 Listener监听器

Quartz 的监听器有Job监听器Trigger监听器Scheduler监听器,对不同层面进行监控。 实际业务用的较多的是 Job监听器,用于监听器是否执行了,其他的用的相对较少,主要讲解Job的

MailJobListener 实现了 JobListener 接口,就必须实现如图所示的4个方法

方法 说明
getName() getName() 方法返回一个字符串用以说明JobListener的名称,对于注册为全局的监听器,getName()主要用于记录日志,对于由特定Job引用的JobListener,注册再JobDetail上的监听器名称必须匹配从监听器上 getName() 方法的返回值
jobToBeExecuted() Scheduler在JobDetail将要被执行时调用这个方法
jobExecutionVetoed() Scheduler在JobDetail即将被执行,但又被TriggerListener否决了时调用这个方法
jobWasExecuted() Scheduler在JobDetail 被执行之后调用这个方法
package cn.quartz.listen;

import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobListener;

public class MailJobListener implements JobListener {

    @Override
    public String getName() {
        return "listener of mail job";
    }

    @Override
    public void jobToBeExecuted(JobExecutionContext jobExecutionContext) {
        System.out.println("准备执行: \t"+jobExecutionContext.getJobDetail().getKey());
    }

    @Override
    public void jobExecutionVetoed(JobExecutionContext jobExecutionContext) {
        System.out.println("取消执行: \t"+jobExecutionContext.getJobDetail().getKey());
    }

    @Override
    public void jobWasExecuted(JobExecutionContext jobExecutionContext, JobExecutionException e) {
        System.out.println("执行结束: \t"+jobExecutionContext.getJobDetail().getKey());

    }
}

调度类

package cn.quartz.listen;

import cn.hutool.json.JSONUtil;
import cn.quartz.simple.MailJob;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.impl.matchers.KeyMatcher;

import java.util.Date;

public class TestQuartz {

    public static void main(String[] args) throws Exception {
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

        Date startTime = DateBuilder.nextGivenSecondDate(null, 8);

        JobDetail job = JobBuilder.newJob(MailJob.class).withIdentity("mailJob", "mailGroup").build();

        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("trigger1", "group1")
                .startNow()
                .withSchedule(SimpleScheduleBuilder.simpleSchedule()
                        .withRepeatCount(2)
                        .withIntervalInSeconds(1))
                .build();

        //增加监听器
        MailJobListener listener = new MailJobListener();
        KeyMatcher<JobKey> keyMatcher = KeyMatcher.keyEquals(job.getKey());
        System.out.println("获取到的 keymatcher :" + JSONUtil.toJsonStr(keyMatcher));
        scheduler.getListenerManager().addJobListener(listener,keyMatcher);

        // schedule it to run!
        scheduler.scheduleJob(job, trigger);
        scheduler.start();

        //等待20秒,让前面的任务都执行完了之后,再关闭调度器
        Thread.sleep(10000);
        scheduler.shutdown(true);
    }
}

1.5 Jdbc store

1.5.1 简介

默认情况,Quartz 的触发器,调度,任务等信息都是放在内存中的,叫做 RAMJobStore。 好处是快速,坏处是一旦系统重启,那么信息就丢失了,就得全部从头来过。
所以Quartz还提供了另一个方式,可以把这些信息存放在数据库做,叫做 JobStoreTX。 好处是就算系统重启了,目前运行到第几次了这些信息都是存放在数据库中的,那么就可以继续原来的步伐把计划任务无缝地继续做下去。 坏处就是性能上比内存慢一些,毕竟数据库读取总是要慢一些的

1.5.2 添加pom依赖

用的是mysql和C3P0连接池,就需要添加这两个的依赖

 <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.27</version>
        </dependency>
        <dependency>
            <groupId>com.mchange</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.5.2</version>
        </dependency>

1.5.3 建表SQL

表名 描述
QRTZ_CALENDARS 存储Quartz的日历信息
QRTZ_CRON_TRIGGERS 存储cron类型的Trigger
包括cron表达式和时区信息
QRTZ_FIRED_TRIGGERS 存储与已触发的Trigger相关的状态信息以及相关联的Job的执行信息
QRTZ_PAUSED_TRIGGER_GRPS 存储已暂停的Trigger组的信息
QRTZ_SCHEDULER_STATE 存储Scheduler相关的状态信息
QRTZ_LOCKS 如果程序使用了悲观锁,则存储程序的悲观锁信息
QRTZ_JOB_DETAILS 存储每一个已经配置的JobDetail信息
QRTZ_SIMPLE_TRIGGERS 存储Simple类型的Trigger,包括重复次数,间隔以及已经触发的次数
QRTZ_BLOG_TRIGGERS 存储Blog类型的Trigger
QRTZ_TRIGGERS 存储已经配置的Trigger的基本信息
QRTZ_TRIGGER_LISTENERS 存储Trigger监听器的信息
QRTZ_JOB_LISTENERS 存储Job监听器信息

其文件在quartz-scheduler下的org/quartz/impl/jdbcjobstore/tables_mysql_innodb.sql

DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS;
DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE;
DROP TABLE IF EXISTS QRTZ_LOCKS;
DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_JOB_DETAILS;
DROP TABLE IF EXISTS QRTZ_CALENDARS;

CREATE TABLE QRTZ_JOB_DETAILS(
SCHED_NAME VARCHAR(120) NOT NULL,
JOB_NAME VARCHAR(190) NOT NULL,
JOB_GROUP VARCHAR(190) NOT NULL,
DESCRIPTION VARCHAR(250) NULL,
JOB_CLASS_NAME VARCHAR(250) NOT NULL,
IS_DURABLE VARCHAR(1) NOT NULL,
IS_NONCONCURRENT VARCHAR(1) NOT NULL,
IS_UPDATE_DATA VARCHAR(1) NOT NULL,
REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
JOB_DATA BLOB NULL,
PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP))
ENGINE=InnoDB;

CREATE TABLE QRTZ_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(190) NOT NULL,
TRIGGER_GROUP VARCHAR(190) NOT NULL,
JOB_NAME VARCHAR(190) NOT NULL,
JOB_GROUP VARCHAR(190) NOT NULL,
DESCRIPTION VARCHAR(250) NULL,
NEXT_FIRE_TIME BIGINT(13) NULL,
PREV_FIRE_TIME BIGINT(13) NULL,
PRIORITY INTEGER NULL,
TRIGGER_STATE VARCHAR(16) NOT NULL,
TRIGGER_TYPE VARCHAR(8) NOT NULL,
START_TIME BIGINT(13) NOT NULL,
END_TIME BIGINT(13) NULL,
CALENDAR_NAME VARCHAR(190) NULL,
MISFIRE_INSTR SMALLINT(2) NULL,
JOB_DATA BLOB NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP))
ENGINE=InnoDB;

CREATE TABLE QRTZ_SIMPLE_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(190) NOT NULL,
TRIGGER_GROUP VARCHAR(190) NOT NULL,
REPEAT_COUNT BIGINT(7) NOT NULL,
REPEAT_INTERVAL BIGINT(12) NOT NULL,
TIMES_TRIGGERED BIGINT(10) NOT NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))
ENGINE=InnoDB;

CREATE TABLE QRTZ_CRON_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(190) NOT NULL,
TRIGGER_GROUP VARCHAR(190) NOT NULL,
CRON_EXPRESSION VARCHAR(120) NOT NULL,
TIME_ZONE_ID VARCHAR(80),
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))
ENGINE=InnoDB;

CREATE TABLE QRTZ_SIMPROP_TRIGGERS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR(190) NOT NULL,
    TRIGGER_GROUP VARCHAR(190) NOT NULL,
    STR_PROP_1 VARCHAR(512) NULL,
    STR_PROP_2 VARCHAR(512) NULL,
    STR_PROP_3 VARCHAR(512) NULL,
    INT_PROP_1 INT NULL,
    INT_PROP_2 INT NULL,
    LONG_PROP_1 BIGINT NULL,
    LONG_PROP_2 BIGINT NULL,
    DEC_PROP_1 NUMERIC(13,4) NULL,
    DEC_PROP_2 NUMERIC(13,4) NULL,
    BOOL_PROP_1 VARCHAR(1) NULL,
    BOOL_PROP_2 VARCHAR(1) NULL,
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
    REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))
ENGINE=InnoDB;

CREATE TABLE QRTZ_BLOB_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(190) NOT NULL,
TRIGGER_GROUP VARCHAR(190) NOT NULL,
BLOB_DATA BLOB NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
INDEX (SCHED_NAME,TRIGGER_NAME, TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))
ENGINE=InnoDB;

CREATE TABLE QRTZ_CALENDARS (
SCHED_NAME VARCHAR(120) NOT NULL,
CALENDAR_NAME VARCHAR(190) NOT NULL,
CALENDAR BLOB NOT NULL,
PRIMARY KEY (SCHED_NAME,CALENDAR_NAME))
ENGINE=InnoDB;

CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_GROUP VARCHAR(190) NOT NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP))
ENGINE=InnoDB;

CREATE TABLE QRTZ_FIRED_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
ENTRY_ID VARCHAR(95) NOT NULL,
TRIGGER_NAME VARCHAR(190) NOT NULL,
TRIGGER_GROUP VARCHAR(190) NOT NULL,
INSTANCE_NAME VARCHAR(190) NOT NULL,
FIRED_TIME BIGINT(13) NOT NULL,
SCHED_TIME BIGINT(13) NOT NULL,
PRIORITY INTEGER NOT NULL,
STATE VARCHAR(16) NOT NULL,
JOB_NAME VARCHAR(190) NULL,
JOB_GROUP VARCHAR(190) NULL,
IS_NONCONCURRENT VARCHAR(1) NULL,
REQUESTS_RECOVERY VARCHAR(1) NULL,
PRIMARY KEY (SCHED_NAME,ENTRY_ID))
ENGINE=InnoDB;

CREATE TABLE QRTZ_SCHEDULER_STATE (
SCHED_NAME VARCHAR(120) NOT NULL,
INSTANCE_NAME VARCHAR(190) NOT NULL,
LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
CHECKIN_INTERVAL BIGINT(13) NOT NULL,
PRIMARY KEY (SCHED_NAME,INSTANCE_NAME))
ENGINE=InnoDB;

CREATE TABLE QRTZ_LOCKS (
SCHED_NAME VARCHAR(120) NOT NULL,
LOCK_NAME VARCHAR(40) NOT NULL,
PRIMARY KEY (SCHED_NAME,LOCK_NAME))
ENGINE=InnoDB;

CREATE INDEX IDX_QRTZ_J_REQ_RECOVERY ON QRTZ_JOB_DETAILS(SCHED_NAME,REQUESTS_RECOVERY);
CREATE INDEX IDX_QRTZ_J_GRP ON QRTZ_JOB_DETAILS(SCHED_NAME,JOB_GROUP);

CREATE INDEX IDX_QRTZ_T_J ON QRTZ_TRIGGERS(SCHED_NAME,JOB_NAME,JOB_GROUP);
CREATE INDEX IDX_QRTZ_T_JG ON QRTZ_TRIGGERS(SCHED_NAME,JOB_GROUP);
CREATE INDEX IDX_QRTZ_T_C ON QRTZ_TRIGGERS(SCHED_NAME,CALENDAR_NAME);
CREATE INDEX IDX_QRTZ_T_G ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_GROUP);
CREATE INDEX IDX_QRTZ_T_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_STATE);
CREATE INDEX IDX_QRTZ_T_N_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP,TRIGGER_STATE);
CREATE INDEX IDX_QRTZ_T_N_G_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_GROUP,TRIGGER_STATE);
CREATE INDEX IDX_QRTZ_T_NEXT_FIRE_TIME ON QRTZ_TRIGGERS(SCHED_NAME,NEXT_FIRE_TIME);
CREATE INDEX IDX_QRTZ_T_NFT_ST ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_STATE,NEXT_FIRE_TIME);
CREATE INDEX IDX_QRTZ_T_NFT_MISFIRE ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME);
CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_STATE);
CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE_GRP ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_GROUP,TRIGGER_STATE);

CREATE INDEX IDX_QRTZ_FT_TRIG_INST_NAME ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,INSTANCE_NAME);
CREATE INDEX IDX_QRTZ_FT_INST_JOB_REQ_RCVRY ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,INSTANCE_NAME,REQUESTS_RECOVERY);
CREATE INDEX IDX_QRTZ_FT_J_G ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,JOB_NAME,JOB_GROUP);
CREATE INDEX IDX_QRTZ_FT_JG ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,JOB_GROUP);
CREATE INDEX IDX_QRTZ_FT_T_G ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP);
CREATE INDEX IDX_QRTZ_FT_TG ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,TRIGGER_GROUP);

commit;

1.5.4 配置文件quartz.properties


#调度标识名 集群中每一个实例都必须使用相同的名称 (区分特定的调度器实例)
org.quartz.scheduler.instanceName = MyScheduler
#ID设置为自动获取 每一个必须不同 (所有调度器实例中是唯一的)
org.quartz.scheduler.instanceId=AUTO

#数据保存方式为持久化
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#表的前缀
org.quartz.jobStore.tablePrefix = 12QRTZ_
#数据库别名
org.quartz.jobStore.dataSource = mysqlDatabase
#加入集群 true 为集群 false不是集群
org.quartz.jobStore.isClustered = false

org.quartz.threadPool.threadCount = 3
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool

#设置数据源
org.quartz.dataSource.mysqlDatabase.driver = com.mysql.jdbc.Driver
org.quartz.dataSource.mysqlDatabase.URL = jdbc:mysql://localhost:3306/quartz?characterEncoding=utf-8
org.quartz.dataSource.mysqlDatabase.user =root
org.quartz.dataSource.mysqlDatabase.password = root
org.quartz.dataSource.mysqlDatabase.maxConnections = 5

1.5.5 Demo实践

package cn.quartz.jdbc;

import org.quartz.*;

import java.text.SimpleDateFormat;
import java.util.Date;

@DisallowConcurrentExecution
public class MailJob implements Job {
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        JobDetail detail = context.getJobDetail();
        String email = detail.getJobDataMap().getString("email");

        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
        String now = sdf.format(new Date());

        System.out.printf("给邮件地址 %s 发出了一封定时邮件, 当前时间是: %s (%s)%n" ,email, now,context.isRecovering());
    }
}
package cn.quartz.jdbc;

import cn.hutool.json.JSONUtil;
import cn.quartz.listen.MailJobListener;

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.impl.matchers.KeyMatcher;

import java.util.Date;

public class TestQuartz {

    public static void main(String[] args) throws Exception{

       try {
            assginNewJob();
        } catch (ObjectAlreadyExistsException e) {
            System.err.println("任务已经存在");
            resumeJobFromDatabase();
        }
    }
    private static void resumeJobFromDatabase() throws Exception {
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        scheduler.start();
        // 等待200秒,让前面的任务都执行完了之后,再关闭调度器
        Thread.sleep(20000);
        scheduler.shutdown(true);
    }


    private static void assginNewJob() throws SchedulerException, InterruptedException {
        // 创建调度器
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        // 定义一个触发器
        Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group1") // 定义名称和所属的租
                .startNow()
                .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(1) // 每隔15秒执行一次
                        .withRepeatCount(2)) // 总共执行11次(第一次执行不基数)
                .build();

        // 定义一个JobDetail
        JobDetail job = JobBuilder.newJob(MailJob.class) // 指定干活的类MailJob
                .withIdentity("mailjob1", "mailgroup") // 定义任务名称和分组
                .usingJobData("email", "admin@10086.com") // 定义属性
                .build();

        // 调度加入这个job
        scheduler.scheduleJob(job, trigger);

        // 启动
        scheduler.start();

        // 等待2秒,让前面的任务都执行完了之后,再关闭调度器
        Thread.sleep(20000);
        scheduler.shutdown(true);
    }
}

1.6 quartz集群

1.6.1 简介

Quartz集群,是指在 基于数据库存储 Quartz调度信息 的基础上, 有多个一模一样的 Quartz 应用在运行。
当某一个Quartz 应用重启或者发生问题的时候, 其他的 Quartz 应用会借助数据库这个桥梁探知到它不行了,从而接手把该进行的 Job 调度工作进行下去。
以这种方式保证任务调度的高可用性,即在发生异常重启等情况下,调度信息依然连贯性地进行下去,就好像 Quartz 应用从来没有中断过似的。

image.png

1.6.2 配置文件quartz.properties


#调度标识名 集群中每一个实例都必须使用相同的名称 (区分特定的调度器实例)
org.quartz.scheduler.instanceName = MyScheduler
#ID设置为自动获取 每一个必须不同 (所有调度器实例中是唯一的)
#要进行集群,多个应用调度id instanceId 必须不一样,这里使用AUTO,就会自动分配不同的ID。 目测是本机机器名称加上时间戳
org.quartz.scheduler.instanceId=AUTO

#数据保存方式为持久化
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#表的前缀
org.quartz.jobStore.tablePrefix = 12QRTZ_
#数据库别名
org.quartz.jobStore.dataSource = mysqlDatabase
#加入集群 true 为集群 false不是集群
org.quartz.jobStore.isClustered = true
#每个一秒钟去数据库检查一下,以在其他应用挂掉之后及时补上
org.quartz.jobStore.clusterCheckinInterval = 1000

org.quartz.threadPool.threadCount = 3
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool

#设置数据源
org.quartz.dataSource.mysqlDatabase.driver = com.mysql.jdbc.Driver
org.quartz.dataSource.mysqlDatabase.URL = jdbc:mysql://localhost:3306/quartz?characterEncoding=utf-8
org.quartz.dataSource.mysqlDatabase.user =root
org.quartz.dataSource.mysqlDatabase.password = root
org.quartz.dataSource.mysqlDatabase.maxConnections = 5

代码部分同jdbc store部分

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 207,113评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,644评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,340评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,449评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,445评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,166评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,442评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,105评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,601评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,066评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,161评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,792评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,351评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,352评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,584评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,618评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,916评论 2 344

推荐阅读更多精彩内容