SpringBoot 之 Quartz 使用

一、Quartz简介

用过Quartz的都懂,Quartz就是一个完全由java编写的开源作业调度框架。

1、组件简介

需要使用这个框架需要知道几个词。

Job

  • Job是一个任务接口,开发者定义自己的任务须实现该接口,并重写execute(JobExecutionContext context)方法.

  • Job中的任务有可能并发执行,例如任务的执行时间过长,而每次触发的时间间隔太短,则会导致任务会被并发执行。

  • 为了避免出现上面的问题,可以在Job实现类上使用@DisallowConcurrentExecution,保证上一个任务执行完后,再去执行下一个任务

JobDetail

  • JobDetail是任务详情。

  • 包含有:任务名称,任务组名称,任务描述、具体任务Job的实现类、参数配置等等信息

  • 可以说JobDetail是任务的定义,而Job是任务的执行逻辑。

Trigger

  • Trigger是一个触发器,定义Job执行的时间规则。

  • 主要触发器:SimpleTrigger,CronTrigger,CalendarIntervalTrigger,DailyTimeIntervalTrigger

  • SimpleTrigger:从某一个时间开始,以一定的时间间隔来执行任务,重复多少次。

  • CronTrigger: 适合于复杂的任务,使用cron表达式来定义执行规则。

  • CalendarIntervalTrigger:指定从某一个时间开始,以一定的时间间隔执行的任务,时间间隔比SimpleTrigger丰富

  • DailyTimeIntervalTrigger:指定每天的某个时间段内,以一定的时间间隔执行任务。并且它可以支持指定星期。

  • 所有的Trigger都包含了StartTimeendTime这两个属性,用来指定Trigger被触发的时间区间。

  • 所有的Trigger都可以设置MisFire策略.

  • MisFire策略是对于由于系统奔溃或者任务时间过长等原因导致Trigger在应该触发的时间点没有触发.

  • 并且超过了misfireThreshold设置的时间(默认是一分钟,没有超过就立即执行)就算misfire(失火)了。

  • 这个时候就该设置如何应对这种变化了。激活失败指令(Misfire Instructions)是触发器的一个重要属性

发生Misfire 对于SimpleTrigger的处理策略

//将任务马上执行一次。对于不会重复执行的任务,这是默认的处理策略。
MISFIRE_INSTRUCTION_FIRE_NOW = 1;

// 调度引擎重新调度该任务,立即执行任务,repeat count 保持不变,
// 按照原有制定的执行方案执行repeat count次,但是,如果当前时间,已经晚于end-time,那么这个触发器将不会再被触发
// 简单的说就是,错过了应该触发的时间没有按时执行,但是最终它还是以原来的重复次数执行,就是会比预计终止的时间晚。
MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT

//和上面的类似,区别就是不会立马执行,而是在下一个激活点执行,且超时期内错过的执行机会作废。                             
MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT

// 这个也是重新调度任务,但是它只按照剩余次数来触发,
// 比如,应该执行10次,但是中间错过了3次没有执行,那它最终只会执行剩余次数 7次。
MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT

//在下一个激活点执行,并重复到指定的次数。
MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT

// 立即执行任务,repeat count 保持不变,就算到了endtime,也继续执行
MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY

发生Misfire 对于其他的Trigger

//立刻执行一次,然后就按照正常的计划执行。
MISFIRE_INSTRUCTION_FIRE_ONCE_NOW

//目前不执行,然后就按照正常的计划执行。这意味着如果下次执行时间超过了end time,实际上就没有执行机会了。
MISFIRE_INSTRUCTION_DO_NOTHING

Scheduler

  • 调度器,主要是用来管理TriggerJobDetail的。

  • Scheduler可以通过组名或者名称来对TriggerJobDetail来进行管理

  • 一个Trigger只能对应一个Job,但是一个Job可以对应多个Trigger.

  • Scheduler 有两个实现类:RemoteSchedulerStdScheduler。但它是由SchdulerFactory创建的。

  • SchdulerFactory是个接口,它有两个实现类:StdSchedulerFactoryDirectSchedulerFactory

2、相关Builder介绍

Quartz提供了相应的Builder方便我们进行构造。

JobBuilder

这个主要方便我们构建任务详情,常用方法:

  • withIdentity(String name, String group):配置Job名称与组名

  • withDescription(String jobDescription): 任务描述

  • requestRecovery(): 出现故障是否重新执行,默认false

  • storeDurably(): 作业完成后是否保留存储,默认false

  • usingJobData(String dataKey, String value): 配置单个参数key

  • usingJobData(JobDataMap newJobDataMap): 配置多个参数,放入一个map

  • setJobData(JobDataMap newJobDataMap): 和上面类似,但是这个参数直接指向newJobDataMap,直接设置的参数无效

TriggerBuilder

这个主要方便我们构建触发器,常用方法:

  • withIdentity(String name, String group): 配置Trigger名称与组名

  • withIdentity(TriggerKey triggerKey): 配置Trigger名称与组名

  • withDescription(String triggerDescription): 描述

  • withPriority(int triggerPriority): 设置优先级,默认是:5

  • startAt(Date triggerStartTime): 设置开始时间

  • startNow(): 触发器立即生效

  • endAt(Date triggerEndTime): 设置结束时间

  • withSchedule(ScheduleBuilder<SBT> schedBuilder): 设置调度builder,下面的builder就是

SimpleScheduleBuilder

几种触发器类型之一,最简单常用的。常用方法:

  • repeatForever():指定触发器将无限期重复

  • withRepeatCount(int triggerRepeatCount):指定重复次数,总触发的次数=triggerRepeatCount+1

  • repeatSecondlyForever(int seconds):每隔seconds秒无限期重复

  • repeatMinutelyForever(int minutes):每隔minutes分钟无限期重复

  • repeatHourlyForever(int hours):每隔hours小时无限期重复

  • repeatSecondlyForever():每隔1秒无限期重复

  • repeatMinutelyForever():每隔1分钟无限期重复

  • repeatHourlyForever():每隔1小时无限期重复

  • withIntervalInSeconds(int intervalInSeconds):每隔intervalInSeconds秒执行

  • withIntervalInMinutes(int intervalInMinutes):每隔intervalInMinutes分钟执行

  • withIntervalInHours(int intervalInHours):每隔intervalInHours小时执行

  • withMisfireHandlingInstructionFireNow():失火后的策略为:MISFIRE_INSTRUCTION_FIRE_NOW

CronScheduleBuilder

算是非常常用的了,crontab 表达式,常用方法:

  • cronSchedule(String cronExpression):使用cron表达式

简单的一笔

CalendarIntervalScheduleBuilder

常用方法:

  • inTimeZone(TimeZone timezone):设置时区

  • withInterval(int timeInterval, IntervalUnit unit):相隔多少时间执行,单位有:毫秒、秒、分、时、天、周、月、年

  • withIntervalInSeconds(int intervalInSeconds):相隔秒

  • withIntervalInWeeks(int intervalInWeeks):相隔周

  • withIntervalInMonths(int intervalInMonths):相隔月

等等方法

DailyTimeIntervalScheduleBuilder

  • withInterval(int timeInterval, IntervalUnit unit):相隔多少时间执行,单位有:秒、分、时,其他单位的不支持会报错

  • withIntervalInSeconds(int intervalInSeconds):相隔秒

  • withIntervalInMinutes(int intervalInMinutes):相隔分

  • withIntervalInHours(int intervalInHours):相隔时

  • onDaysOfTheWeek(Set<Integer> onDaysOfWeek):将触发器设置为在一周的指定日期触发。取值范围可以是1-7,1是星期天,2是星期一…

  • onDaysOfTheWeek(Integer ... onDaysOfWeek):和上面一样,3是星期二…7是星期六

  • onMondayThroughFriday():每星期的周一导周五触发

  • onSaturdayAndSunday():每星期的周六周日触发

  • onEveryDay():每天触发

  • withRepeatCount(int repeatCount):重复次数,总的重复次数=1 (at start time) + repeatCount

  • startingDailyAt(TimeOfDay timeOfDay):触发的开始时间

  • endingDailyAt(TimeOfDay timeOfDay):触发的结束时间

3、基本使用

引用jar,就可以玩了。

引用依赖

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

在pom.xml文件中,引入

简单例子:

public class Demo {
    public static final String COUNT = "count";

    //这个属性如不是static,那么每次都要实例这个任务类,始终打印为: 1
    private static int num = 1;

    public static void main(String[] args) throws SchedulerException {
        SchedulerFactory schedulerfactory = new StdSchedulerFactory();
//        Scheduler scheduler = schedulerfactory.getScheduler();
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        scheduler.start();
        JobDetail job = JobBuilder.newJob(HelloJob.class).withIdentity("jobName", "jobGroupName")
                .usingJobData(COUNT,num).build();
        // 各种builder 的基本使用
        SimpleScheduleBuilder simpleScheduleBuilder = SimpleScheduleBuilder.simpleSchedule().repeatForever().withIntervalInSeconds(3);
        CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule("*/3 * * * * ?");
        CalendarIntervalScheduleBuilder calendarIntervalScheduleBuilder = CalendarIntervalScheduleBuilder.calendarIntervalSchedule().withInterval(3, DateBuilder.IntervalUnit.SECOND);
        DailyTimeIntervalScheduleBuilder dailyTimeIntervalScheduleBuilder = DailyTimeIntervalScheduleBuilder.dailyTimeIntervalSchedule().withIntervalInSeconds(3);

        Trigger trigger =  TriggerBuilder.newTrigger().withIdentity("jobName", "jobGroupName")
                .withSchedule(dailyTimeIntervalScheduleBuilder)
                .startNow().build();
        scheduler.scheduleJob(job, trigger);
    }

    // 保存在JobDataMap传递的参数
    @PersistJobDataAfterExecution
    @DisallowConcurrentExecution
    public static class HelloJob implements Job{
        @Override
        public void execute(JobExecutionContext context) throws JobExecutionException {
            JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
            int count = jobDataMap.getInt(COUNT);
            System.out.println("=========="+ LocalDateTimeUtils.formatDateTimeNow()+",count="+count);
            jobDataMap.put(COUNT,++num);
        }
    }
}

是不是感觉很简单,自己定义一个类,然后实现Job接口,重写execute(JobExecutionContext context) 方法,然后使用调度器去调度一下即可。
简单玩完了,和springboot整合一波。

二、与Springboot整合

这个才是重点,Springboot基本是一个Java程序猿必备的技能了。什么框架都得和它整一下。

1、引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-quartz</artifactId>
</dependency>

spring官方自己都帮我们搞好了一些配置。

2、配置application.yml

如下:

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/quartz?serverTimezone=GMT&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      initialSize: 5
      minIdle: 5
      maxActive: 30
      max-wait: 6000
      pool-prepared-statements: true
      max-pool-prepared-statement-per-connection-size: 20
      time-between-eviction-runs-millis: 60000
      min-evictable-idle-time-millis: 300000
      #validation-query: SELECT 1 FROM DUAL
      test-while-idle: true
      test-on-borrow: false
      test-on-return: false
      stat-view-servlet:
        enabled: true
        url-pattern: /druid/*
        #login-username: admin
        #login-password: admin
      filter:
        stat:
          log-slow-sql: true
          slow-sql-millis: 1000
          merge-sql: false
        wall:
          config:
            multi-statement-allow: true
  quartz:
    job-store-type: jdbc #数据库方式
    #是否等待任务执行完毕后,容器才会关闭
    wait-for-jobs-to-complete-on-shutdown=false
    #配置的job是否覆盖已经存在的JOB信息
    overwrite-existing-jobs: false
    jdbc:
      initialize-schema: ALWAYS #不初始化表结构
    properties:
      org:
        quartz:
          scheduler:
            instanceId: AUTO #默认主机名和时间戳生成实例ID,可以是任何字符串,但对于所有调度程序来说,必须是唯一的 对应qrtz_scheduler_state INSTANCE_NAME字段
            #instanceName: clusteredScheduler #quartzScheduler
          jobStore:
            class: org.quartz.impl.jdbcjobstore.JobStoreTX #持久化配置
            driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate #我们仅为数据库制作了特定于数据库的代理
            useProperties: false #以指示JDBCJobStore将JobDataMaps中的所有值都作为字符串,因此可以作为名称 - 值对存储而不是在BLOB列中以其序列化形式存储更多复杂的对象。从长远来看,这是更安全的,因为您避免了将非String类序列化为BLOB的类版本问题。
            tablePrefix: qrtz_  #数据库表前缀
            misfireThreshold: 60000 #在被认为“失火”之前,调度程序将“容忍”一个Triggers将其下一个启动时间通过的毫秒数。默认值(如果您在配置中未输入此属性)为60000(60秒)。
            clusterCheckinInterval: 5000 #设置此实例“检入”*与群集的其他实例的频率(以毫秒为单位)。影响检测失败实例的速度。
            isClustered: true #打开群集功能
          threadPool: #连接池
            class: org.quartz.simpl.SimpleThreadPool
            threadCount: 10
            threadPriority: 5
            threadsInheritContextClassLoaderOfInitializingThread: true
  • 这里打算搞个集群版的,就算你部署多台服务器,定时任务会自己负载均衡,不会每台服务器都执行。

  • 表的生成,其实可以修改配置,启动的时候自己在数据库生成表。操作方法:

  • 修改:spring.quartz.jdbc.initialize-schema: ALWAYS、说明一下

  • 这里有3个值可选:ALWAYS(每次都生成)、EMBEDDED(仅初始化嵌入式数据源)、NEVER(不初始化数据源)。

  • 表生成之后,再改为never即可。注意一点就是我测试了下,发现只有使用druid数据库连接池才会自动生成表

3、表的说明

会自动生成的表如下

//以Blob 类型存储的触发器。 
qrtz_blob_triggers

//存放日历信息, quartz可配置一个日历来指定一个时间范围。 
qrtz_calendars

//存放cron类型的触发器。 
qrtz_cron_triggers
//存储已经触发的trigger相关信息,trigger随着时间的推移状态发生变化,直到最后trigger执行完成,从表中被删除。 
qrtz_fired_triggers    

//存放一个jobDetail信息。 
qrtz_job_details

//job**监听器**。 
qrtz_job_listeners

//Quartz提供的锁表,为多个节点调度提供分布式锁,实现分布式调度,默认有2个锁
qrtz_locks

//存放暂停掉的触发器。
qrtz_paused_trigger_graps

//存储所有节点的scheduler,会定期检查scheduler是否失效
qrtz_scheduler_state

//存储SimpleTrigger 
qrtz_simple_triggers

//触发器监听器。 
qrtz_trigger_listeners

//触发器的基本信息。
qrtz_triggers

//存储CalendarIntervalTrigger和DailyTimeIntervalTrigger两种类型的触发器
qrtz_simprop_triggers

重要表字段解析

CREATE TABLE `qrtz_job_details` (
  `SCHED_NAME` varchar(120) COLLATE utf8_bin NOT NULL COMMENT '调度器名,集群环境中使用,必须使用同一个名称——集群环境下”逻辑”相同的scheduler,默认为QuartzScheduler',
  `JOB_NAME` varchar(200) COLLATE utf8_bin NOT NULL COMMENT '集群中job的名字',
  `JOB_GROUP` varchar(200) COLLATE utf8_bin NOT NULL COMMENT '集群中job的所属组的名字',
  `DESCRIPTION` varchar(250) COLLATE utf8_bin DEFAULT NULL COMMENT '描述',
  `JOB_CLASS_NAME` varchar(250) COLLATE utf8_bin NOT NULL COMMENT '集群中个note job实现类的完全包名,quartz就是根据这个路径到classpath找到该job类',
  `IS_DURABLE` varchar(1) COLLATE utf8_bin NOT NULL COMMENT '是否持久化,把该属性设置为1,quartz会把job持久化到数据库中',
  `IS_NONCONCURRENT` varchar(1) COLLATE utf8_bin NOT NULL COMMENT '是否并行,该属性可以通过注解配置',
  `IS_UPDATE_DATA` varchar(1) COLLATE utf8_bin NOT NULL,
  `REQUESTS_RECOVERY` varchar(1) COLLATE utf8_bin NOT NULL COMMENT '当一个scheduler失败后,其他实例可以发现那些执行失败的Jobs,若是1,那么该Job会被其他实例重新执行,否则对应的Job只能释放等待下次触发',
  `JOB_DATA` blob COMMENT '一个blob字段,存放持久化job对象',
  PRIMARY KEY (`SCHED_NAME`,`JOB_NAME`,`JOB_GROUP`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='存储每一个已配置的 Job 的详细信息';


CREATE TABLE `qrtz_triggers` (
  `SCHED_NAME` varchar(120) COLLATE utf8_bin NOT NULL COMMENT '调度器名,和配置文件org.quartz.scheduler.instanceName保持一致',
  `TRIGGER_NAME` varchar(200) COLLATE utf8_bin NOT NULL COMMENT '触发器的名字',
  `TRIGGER_GROUP` varchar(200) COLLATE utf8_bin NOT NULL COMMENT '触发器所属组的名字',
  `JOB_NAME` varchar(200) COLLATE utf8_bin NOT NULL COMMENT 'qrtz_job_details表job_name的外键',
  `JOB_GROUP` varchar(200) COLLATE utf8_bin NOT NULL COMMENT 'qrtz_job_details表job_group的外键',
  `DESCRIPTION` varchar(250) COLLATE utf8_bin DEFAULT NULL COMMENT '描述',
  `NEXT_FIRE_TIME` bigint(13) DEFAULT NULL COMMENT '下一次触发时间',
  `PREV_FIRE_TIME` bigint(13) DEFAULT NULL COMMENT '上一次触发时间',
  `PRIORITY` int(11) DEFAULT NULL COMMENT '线程优先级',
  `TRIGGER_STATE` varchar(16) COLLATE utf8_bin NOT NULL COMMENT '当前trigger状态',
  `TRIGGER_TYPE` varchar(8) COLLATE utf8_bin NOT NULL COMMENT '触发器类型',
  `START_TIME` bigint(13) NOT NULL COMMENT '开始时间',
  `END_TIME` bigint(13) DEFAULT NULL COMMENT '结束时间',
  `CALENDAR_NAME` varchar(200) COLLATE utf8_bin DEFAULT NULL COMMENT '日历名称',
  `MISFIRE_INSTR` smallint(2) DEFAULT NULL COMMENT 'misfire处理规则,1代表【以当前时间为触发频率立刻触发一次,然后按照Cron频率依次执行】,
   2代表【不触发立即执行,等待下次Cron触发频率到达时刻开始按照Cron频率依次执行�】,
   -1代表【以错过的第一个频率时间立刻开始执行,重做错过的所有频率周期后,当下一次触发频率发生时间大于当前时间后,再按照正常的Cron频率依次执行】',
  `JOB_DATA` blob COMMENT 'JOB存储对象',
  PRIMARY KEY (`SCHED_NAME`,`TRIGGER_NAME`,`TRIGGER_GROUP`),
  KEY `SCHED_NAME` (`SCHED_NAME`,`JOB_NAME`,`JOB_GROUP`),
  CONSTRAINT `qrtz_triggers_ibfk_1` FOREIGN KEY (`SCHED_NAME`, `JOB_NAME`, `JOB_GROUP`) REFERENCES `qrtz_job_details` (`SCHED_NAME`, `JOB_NAME`, `JOB_GROUP`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='存储已配置的 Trigger 的信息';


CREATE TABLE `qrtz_cron_triggers` (
  `SCHED_NAME` varchar(120) COLLATE utf8_bin NOT NULL COMMENT '集群名',
  `TRIGGER_NAME` varchar(200) COLLATE utf8_bin NOT NULL COMMENT '调度器名,qrtz_triggers表trigger_name的外键',
  `TRIGGER_GROUP` varchar(200) COLLATE utf8_bin NOT NULL COMMENT 'qrtz_triggers表trigger_group的外键',
  `CRON_EXPRESSION` varchar(200) COLLATE utf8_bin NOT NULL COMMENT 'cron表达式',
  `TIME_ZONE_ID` varchar(80) COLLATE utf8_bin DEFAULT NULL COMMENT '时区ID',
  PRIMARY KEY (`SCHED_NAME`,`TRIGGER_NAME`,`TRIGGER_GROUP`),
  CONSTRAINT `qrtz_cron_triggers_ibfk_1` FOREIGN KEY (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) REFERENCES `qrtz_triggers` (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='存放cron类型的触发器';

CREATE TABLE `qrtz_scheduler_state` (
  `SCHED_NAME` varchar(120) COLLATE utf8_bin NOT NULL COMMENT '调度器名称,集群名',
  `INSTANCE_NAME` varchar(200) COLLATE utf8_bin NOT NULL COMMENT '集群中实例ID,配置文件中org.quartz.scheduler.instanceId的配置',
  `LAST_CHECKIN_TIME` bigint(13) NOT NULL COMMENT '上次检查时间',
  `CHECKIN_INTERVAL` bigint(13) NOT NULL COMMENT '检查时间间隔',
  PRIMARY KEY (`SCHED_NAME`,`INSTANCE_NAME`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='调度器状态';


CREATE TABLE `qrtz_fired_triggers` (
  `SCHED_NAME` varchar(120) COLLATE utf8_bin NOT NULL COMMENT '调度器名称,集群名',
  `ENTRY_ID` varchar(95) COLLATE utf8_bin NOT NULL COMMENT '运行Id',
  `TRIGGER_NAME` varchar(200) COLLATE utf8_bin NOT NULL COMMENT '触发器名',
  `TRIGGER_GROUP` varchar(200) COLLATE utf8_bin NOT NULL COMMENT '触发器组',
  `INSTANCE_NAME` varchar(200) COLLATE utf8_bin NOT NULL COMMENT '集群中实例ID',
  `FIRED_TIME` bigint(13) NOT NULL COMMENT '触发时间',
  `SCHED_TIME` bigint(13) NOT NULL COMMENT '计划时间',
  `PRIORITY` int(11) NOT NULL COMMENT '线程优先级',
  `STATE` varchar(16) COLLATE utf8_bin NOT NULL COMMENT '状态',
  `JOB_NAME` varchar(200) COLLATE utf8_bin DEFAULT NULL COMMENT '任务名',
  `JOB_GROUP` varchar(200) COLLATE utf8_bin DEFAULT NULL COMMENT '任务组',
  `IS_NONCONCURRENT` varchar(1) COLLATE utf8_bin DEFAULT NULL COMMENT '是否并行',
  `REQUESTS_RECOVERY` varchar(1) COLLATE utf8_bin DEFAULT NULL COMMENT '是否恢复',
  PRIMARY KEY (`SCHED_NAME`,`ENTRY_ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='存储与已触发的 Trigger 相关的状态信息,以及相联 Job 的执行信息';


CREATE TABLE `qrtz_simple_triggers` (
  `SCHED_NAME` varchar(120) COLLATE utf8_bin NOT NULL COMMENT '调度器名,集群名',
  `TRIGGER_NAME` varchar(200) COLLATE utf8_bin NOT NULL COMMENT '触发器名',
  `TRIGGER_GROUP` varchar(200) COLLATE utf8_bin NOT NULL COMMENT '触发器组',
  `REPEAT_COUNT` bigint(7) NOT NULL COMMENT '重复次数',
  `REPEAT_INTERVAL` bigint(12) NOT NULL COMMENT '重复间隔',
  `TIMES_TRIGGERED` bigint(10) NOT NULL COMMENT '已触发次数',
  PRIMARY KEY (`SCHED_NAME`,`TRIGGER_NAME`,`TRIGGER_GROUP`),
  CONSTRAINT `qrtz_simple_triggers_ibfk_1` FOREIGN KEY (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) REFERENCES `qrtz_triggers` (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='存储简单的 Trigger,包括重复次数,间隔,以及已触的次数';

4、怎么使用

  • 当我们引入spring-boot-starter-quartz的依赖后,springboot在启动的时候会自动加载配置类:org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration

  • 会帮我们初始化好调度器,源码:SchedulerFactoryBean实现InitializingBean,重写afterPropertiesSet() 里面就有了。

  • 而且Scheduler,Spring默认是帮我们启动的,不需要手动启动。

  • 使用如下:

@Autowired
private Scheduler scheduler;

spring会自己注入,通过工厂类SchedulerFactoryBeangetObject()方法。
搞个基本的增删改查,很简单,代码也没怎么优化,就这样吧。

@Component
public class JobService {

    @Autowired
    private Scheduler scheduler;


    /**
     * 增加一个job
     *
     * @param jobClass
     *            任务实现类
     * @param jobName
     *            任务名称(建议唯一)
     * @param jobGroupName
     *            任务组名
     * @param cron
     *            时间表达式 (如:0/5 * * * * ? )
     * @param jobData
     *            参数
     */
    public void addJob(Class<? extends Job> jobClass, String jobName, String jobGroupName, String cron, Map jobData,Date endTime)
        throws SchedulerException {
        // 创建jobDetail实例,绑定Job实现类
        // 指明job的名称,所在组的名称,以及绑定job类
        // 任务名称和组构成任务key
        JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(jobName, jobGroupName).build();
        // 设置job参数
        if (jobData != null && jobData.size() > 0) {
            jobDetail.getJobDataMap().putAll(jobData);
        }
        // 定义调度触发规则
        // 使用cornTrigger规则
        // 触发器key
        Trigger trigger = TriggerBuilder.newTrigger().withIdentity(jobName, jobGroupName)
            .startAt(DateBuilder.futureDate(1, DateBuilder.IntervalUnit.SECOND))
            .withSchedule(CronScheduleBuilder.cronSchedule(cron)).startNow().build();
        if(endTime!=null){
            trigger.getTriggerBuilder().endAt(endTime);
        }
        // 把作业和触发器注册到任务调度中
        scheduler.scheduleJob(jobDetail, trigger);
    }



    public Trigger getTrigger(String jobName, String jobGroupName) throws SchedulerException {
        return scheduler.getTrigger(TriggerKey.triggerKey(jobName, jobGroupName));
    }

    /**
     * 删除任务一个job
     *
     * @param jobName
     *            任务名称
     * @param jobGroupName
     *            任务组名
     */
    public void deleteJob(String jobName, String jobGroupName) {
        try {
            scheduler.deleteJob(JobKey.jobKey(jobName, jobGroupName));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 暂停一个job
     *
     * @param jobName
     * @param jobGroupName
     */
    public void pauseJob(String jobName, String jobGroupName) {
        try {
            JobKey jobKey = JobKey.jobKey(jobName, jobGroupName);
            scheduler.pauseJob(jobKey);
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
    }

    /**
     * 恢复一个job
     *
     * @param jobName
     * @param jobGroupName
     */
    public void resumeJob(String jobName, String jobGroupName) {
        try {
            JobKey jobKey = JobKey.jobKey(jobName, jobGroupName);
            scheduler.resumeJob(jobKey);
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
    }

    /**
     * 立即执行一个job
     *
     * @param jobName
     * @param jobGroupName
     */
    public void runJobNow(String jobName, String jobGroupName) {
        try {
            JobKey jobKey = JobKey.jobKey(jobName, jobGroupName);
            scheduler.triggerJob(jobKey);
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
    }

    /**
     * 修改 一个job的 时间表达式
     *
     * @param jobName
     * @param jobGroupName
     * @param cron
     */
    public void updateJob(String jobName, String jobGroupName, String cron,Map jobData) {
        try {
            TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroupName);
            Trigger trigger = scheduler.getTrigger(triggerKey);
            if(trigger instanceof CronTrigger){
                CronTrigger cronTrigger = (CronTrigger) trigger;
                cronTrigger = cronTrigger.getTriggerBuilder().withIdentity(triggerKey)
                        .withSchedule(CronScheduleBuilder.cronSchedule(cron)).build();
            }else {
                // 这里其实可以删掉在添加即可,懒得写了
                throw new RuntimeException("格式转换错误");
            }
            // 设置job参数
            if (jobData != null && jobData.size() > 0) {
                trigger.getJobDataMap().putAll(jobData);
            }
            // 重启触发器
            scheduler.rescheduleJob(triggerKey, trigger);
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
    }
}
  • 和上面得基本demo没什么两样,就是scheduler 并不需要我们去创建了。

  • 当部署多个服务时,也不会重复执行。且任务会负载均衡分配。

5、时间演练

Quartz 提供了下一次运行的时间,我们可以通过下一次运行的时间,比对是否符合我们的预期

public class Test {
    public static void main(String[] args) throws ParseException, SchedulerException {
        String cron = "0/10 * * * * ?";
        CronExpression cronExpression = new CronExpression(cron);
        Date nextValidTimeAfter = cronExpression.getNextValidTimeAfter(new Date());
        System.out.println("nextValidTimeAfter=" + nextValidTimeAfter);
        nextValidTimeAfter = cronExpression.getNextValidTimeAfter(nextValidTimeAfter);
        System.out.println("nextValidTimeAfter=" + nextValidTimeAfter);
        nextValidTimeAfter = cronExpression.getNextValidTimeAfter(nextValidTimeAfter);
        System.out.println("nextValidTimeAfter=" + nextValidTimeAfter);
        CalendarIntervalTrigger calendarIntervalTrigger =
            newTrigger().withIdentity("trigger3", "group1")
                .withSchedule(calendarIntervalSchedule().withIntervalInWeeks(3)).build();

        Date nextFireTime = calendarIntervalTrigger.getFireTimeAfter(new Date());
        System.out.println("nextFireTime=" + DateUtils.formatTime(nextFireTime));
        nextFireTime = calendarIntervalTrigger.getFireTimeAfter(nextFireTime);
        System.out.println("nextFireTime=" + DateUtils.formatTime(nextFireTime));
        nextFireTime = calendarIntervalTrigger.getFireTimeAfter(nextFireTime);
        System.out.println("nextFireTime=" + DateUtils.formatTime(nextFireTime));
        nextFireTime = calendarIntervalTrigger.getFireTimeAfter(nextFireTime);
        System.out.println("nextFireTime=" + DateUtils.formatTime(nextFireTime));

    }
}

每个 Tigger 都有一个Date getFireTimeAfter(Date afterTime) 的方法,返回的就是下一次运行的时间。

  • 作者:rstyro

  • 链接: https://rstyro.github.io/blog/2019/10/28/Quartz介绍与SpringBoot使用/

来源:https://mp.weixin.qq.com/s/Hnoi7GtESnH-yfETO53L9g

 

posted @ 2023-02-10 22:46  程序员小明1024  阅读(957)  评论(0编辑  收藏  举报