# Quartz教程 - 8 持久化与集群
为什么要持久化?
默认情况下 Quartz 是保存在内存中的,如果不做持久化,系统停止或重启,任务将丢失;
另外如果做了持久化,可以在数据库中查看到有多少 JobDetail 和 Trigger。
为什么需要集群?
因为每台服务器运行的代码是一样的,那么一个定时任务如果被多个服务器运行,就会存在重复执行,另外集群可以保证系统的高可用性,一台服务器发生故障,还能保证系统的稳定运行。
集群是必须利用数据库将 Quartz 的信息保存到数据库中的,每台服务器通过读取数据库的数据抢占执行权限,因此将Quartz数据持久化到数据库是集群的基础。因此先讲解 Quartz 的持久化。
# 8.1 持久化
Quartz 持久化需要使用数据库,并在数据库中建立一些表,我们可以将这些表建在业务数据库中,也可以将这些表建立在独立的数据库中,如果建立在独立的数据库中,那么我们的系统需要配置多个数据源。一般情况下,建立在业务相关的数据库中即可。
# 1 引入依赖
需要在 SpringBoot 项目中,引入数据库连接和数据源的依赖。
在项目的 pom.xml 中添加依赖。
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
<!-- 引入Spring封装的jdbc,内部默认依赖了 HikariDataSource 数据源-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
2
3
4
5
6
7
8
9
10
11
# 2 创建Job
和之前的是一样的 Job 类。
MyJob.java
package com.doubibiji.springbootquartz.job;
import lombok.extern.slf4j.Slf4j;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;
@Slf4j
public class MyJob extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
log.info("执行定时任务");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# 3 注册Job和Trigger
和之前的操作一样,创建一个 Quartz 的配置类,在这个配置类中,创建 JobDetail 和 Trigger,并将他们添加到Spring 容器中。
Quartz.config
package com.doubibiji.springbootquartz.config;
import com.doubibiji.springbootquartz.job.MyJob;
import org.quartz.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class QuartzConfig {
/**
* 将Job注册到Spring容器
*/
@Bean
public JobDetail myJob(){
//newJob方法就是在绑定要运行的Job接口实现类,需要实现类的反射做参数
return JobBuilder.newJob(MyJob.class)
// 给当前JobDetail对象在调度环境中起名
.withIdentity("myJob")
// 将JobDetail存储在 Scheduler中,即使没有触发器绑定当前JobDetail对象,也不会被删除
.storeDurably()
.build();
}
// 下面是触发器的声明,也会保存到Spring容器中
@Bean
public Trigger myTrigger(){
// 定义Cron表达式
CronScheduleBuilder cron = CronScheduleBuilder.cronSchedule("*/5 * * * * ?"); // 每5秒执行一次
return TriggerBuilder.newTrigger()
// 绑定要运行的JobDetail对象
.forJob(myJob())
.startAt(DateBuilder.futureDate(20, DateBuilder.IntervalUnit.SECOND)) // 延迟20秒执行
// 为触发器起名
.withIdentity("myTrigger")
// 绑定cron表达式
.withSchedule(cron)
.build();
}
}
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
# 4 配置数据源
在项目的 application.yml 中配置数据源。
这里 Quartz 和业务功能使用同一个数据源,所以不用单独为 Quartz 单独配置数据源。
server:
port: 8080
spring:
# 数据源配置
datasource:
url: jdbc:mysql://localhost:3306/doubibiji-db?allowMultiQueries=true&useSSL=false&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.zaxxer.hikari.HikariDataSource
hikari:
minimum-idle: 3
maximum-pool-size: 10
idle-timeout: 10000
connection-test-query: select 1
initialization-fail-timeout: 15000
connection-timeout: 15000
# quartz 配置
quartz:
job-store-type: jdbc # 存储类型使用数据库
jdbc:
initialize-schema: always
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
需要注意:
这里的 initialize-schema
表示初始化 Quartz 数据库表的配置,always 表示每次启动项目都会删除然后重新创建 Quartz 的表,这样是不对的。所以在第一次启动的时候,将 initialize-schema
的值设置为 always
,后面再启动项目库,需要将这个值修改为 never
。
创建表的SQL脚本可以在quartz依赖的jar包中找到。
此时的项目结构:
# 5 运行项目
运行项目,可以看到定时任务每隔5秒运行一次。
同时数据库会生成 Quartz 的运行数据。
# 6 Quartz数据库表
运行项目后,会在数据库中生成一些表,如下:
这里简单介绍一下 Quartz 创建的数据库表。
表名 | 说明 |
---|---|
QRTZ_BLOG_TRIGGERS | Trigger 作为 Blob 类型存储(用于 Quartz 用户用 JDBC 创建他们自己定制的 Trigger 类型,JobStore 并不知道如何存储实例的时候) |
QRTZ_CALENDARS | 以 Blob 类型存储 Quartz 的 Calendar 日历信息 |
QRTZ_CRON_TRIGGERS | 存储 Cron Trigger,包括 Cron 表达式和时区信息 |
QRTZ_FIRED_TRIGGERS | 存储与已触发的 Trigger 相关的状态信息,以及相联 Job 的执行信息 |
QRTZ_JOB_DETAILS | 存储 Job 的详细信息 |
QRTZ_LOCKS | 在集群环境下,用于实现分布式锁的表格,防止多个节点同时执行同一个作业或触发器 |
QRTZ_PAUSED_TRIGGER_GRPS | 存储被暂停的触发器组信息 |
QRTZ_SCHEDULER_STATE | 存储调度程序的状态信息,例如调度器的标识、调度器实例的最近检测时间等。 |
QRTZ_SIMPLE_TRIGGERS | 存储简单触发器信息,如重复次数、重复间隔等 |
QRTZ_SIMPROP_TRIGGERS | 存储CalendarIntervalTrigger和DailyTimeIntervalTrigger两种类型的触发器 |
QRTZ_TRIGGERS | 存储触发器的详细信息,包括触发器的名称、组名、触发类型等 |
# 8.2 集群
集群的前提是必须持久化。
通过集群 Quartz 会将任务分配到不同的机器上,每个任务只会被分配到其中一台机器上。不能一个任务同时在多台机器上同时重复触发。
在上面持久化的基础上,继续完成。
# 1 配置
进行集群,最少要在 application.yaml 中添加 Quartz 集群配置如下:
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost:3306/doubibiji-db?allowMultiQueries=true&useSSL=false&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.zaxxer.hikari.HikariDataSource
hikari:
minimum-idle: 3
maximum-pool-size: 10
idle-timeout: 10000
connection-test-query: select 1
initialization-fail-timeout: 15000
connection-timeout: 15000
# quartz 配置
quartz:
job-store-type: jdbc # 使用jdbc的方式持久化
jdbc:
initialize-schema: never # 启动后,不创建数据库表
# 属性配置
properties:
org:
quartz:
# 集群配置
scheduler:
instanceName: MyScheduler # 名称自定义,一起集群的各个服务器,名称要一致
instanceId: AUTO # 实例ID,各个集群的服务器,不能重复,这里使用自动生成就好了
jobStore:
isClustered: true # 是否进行集群配置
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
# 2 运行
为了演示方便,将项目拷贝成两份,修改端口后同时运行,就可以在两个客户端同时运行定时任务了。
运行起来后,你有可能会发现定时任务一直只会在一个项目定时执行,另外一个项目没有动静,这其实是因为另一个项目一直抢不到执行的权限,这个其实是没有问题的,如果关闭调抢到执行权限的项目,你就会发现另一个项目就会执行了。
# 3 其他配置
Quartz 还有一些其他的配置,这里简单举例:
spring:
# quartz 配置
quartz:
job-store-type: jdbc # 使用jdbc的方式持久化
jdbc:
initialize-schema: never # 启动后,不创建数据库表
auto-startup: true # 启动开关为开启状态
startup-delay: 1s # 启动延迟1秒
overwrite-existing-jobs: true # 启动时更新已存在的任务
# 属性配置
properties:
org:
quartz:
# 集群配置
scheduler:
instanceName: MyScheduler # 名称自定义,一起集群的各个服务器,名称要一致
instanceId: AUTO # 实例ID,各个集群的服务器,不能重复,这里使用自动生成就好了
jobStore:
isClustered: true # 是否进行集群配置
tablePrefix: QRTZ_ # 数据库表前缀为QRTZ_,数据库表名称前缀可以修改
clusterCheckinInterval: 10000 # 集群检查间隔为 15000 毫秒
useProperties: false
misfireThreshold: 12000 # 设置任务调度器的 Misfire 阈值为 12000 毫秒
# 线程池配置
threadPool:
class: org.quartz.simpl.SimpleThreadPool # 使用 SimpleThreadPool 线程池
threadCount: 3 # 设置线程池中线程的数量为3
threadPriority: 5 # 设置线程池中线程的优先级为 5
threadsInheritContextClassLoaderOfInitializingThread: true # 设置线程是否继承初始化线程的类加载器为 true
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