0%

Quartz介绍与SpringBoot使用

一、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的处理策略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//将任务马上执行一次。对于不会重复执行的任务,这是默认的处理策略。
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

1
2
3
4
5
//立刻执行一次,然后就按照正常的计划执行。
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,就可以玩了。

引用依赖

1
2
3
4
5
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.2</version>
</dependency>

在pom.xml文件中,引入

简单例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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、引入依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>

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

2、配置application.yml

如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
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、表的说明

会自动生成的表如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//以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

重要表字段解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
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默认是帮我们启动的,不需要手动启动。
  • 使用如下:
    1
    2
    @Autowired
    private Scheduler scheduler;

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
@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 提供了下一次运行的时间,我们可以通过下一次运行的时间,比对是否符合我们的预期

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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) 的方法,返回的就是下一次运行的时间。

您的打赏,是我创作的动力!不给钱?那我只能靠想象力充饥了。