SpringBoot-引入Quartz并进行微服务隔离
Quartz引入
介绍
Quartz 是 Java 领域最著名的开源任务调度工具。
在上篇文章中,我们详细的介绍了 Quartz 的单体应用实践,如果只在单体环境中应用,Quartz 未必是最好的选择,例如Spring Scheduled
一样也可以实现任务调度,并且与SpringBoot
无缝集成,支持注解配置,非常简单,但是它有个缺点就是在集群环境下,会导致任务被重复调度!
而与之对应的 Quartz 提供了极为广用的特性,如任务持久化、集群部署和分布式调度任务等等,正因如此,基于 Quartz 任务调度功能在系统开发中应用极为广泛!
在集群环境下,Quartz 集群中的每个节点是一个独立的 Quartz 应用,没有负责集中管理的节点,而是通过数据库表来感知另一个应用,利用数据库锁的方式来实现集群环境下进行并发控制,每个任务当前运行的有效节点有且只有一个!
特别需要注意的是:分布式部署时需要保证各个节点的系统时间一致!
数据表初始化
数据库表结构官网已经提供,我们可以直接访问Quartz
对应的官方网站,找到对应的版本,然后将其下载!
因为使用的是2.2.5.RELEASE
版本的spring-boot,因此对应的quartz版本为2.3.2
,通过quartz下载地址http://www.quartz-scheduler.org/downloads/
因为没有2.3.2版本的文件,因此我下载了quartz-2.3.0-distribution.tar.gz
下载完成之后将其解压,在文件中搜索sql
,在里面选择适合当前环境的数据库脚本文件,然后将其初始化到数据库中即可!
因为使用是Mariadb,因此选用的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;
具体表描述如下:
表名 | 描述 |
---|---|
QRTZ_BLOB_TRIGGERS | Trigger作为Blob类型存储 |
QRTZ_CALENDARS | 存储Quartz的Calendar信息 |
QRTZ_CRON_TRIGGERS | 存储CronTrigger,包括Cron表达式和时区信息 |
QRTZ_FIRED_TRIGGERS | 存储与已触发的Trigger相关的状态信息,以及相联Job的执行信息 |
QRTZ_JOB_DETAILS | 存储每一个已配置的Job的详细信息 |
QRTZ_LOCKS | 存储程序的悲观锁的信息 |
QRTZ_PAUSED_TRIGGER_GRPS | 存储已暂停的Trigger组的信息 |
QRTZ_SCHEDULER_STATE | 存储少量的有关Scheduler的状态信息和别的Scheduler实例 |
QRTZ_SIMPLE_TRIGGERS | 存储简单的Trigger,包括重复次数、间隔、以及已触发的次数 |
QRTZ_SIMPROP_TRIGGERS | 存储CalendarIntervalTrigger和DailyTimeIntervalTrigger两种类型的触发器 |
QRTZ_TRIGGERS | 存储已配置的Trigger的信息 |
这里创建了一个名为quartz-config的数据库作为Quartz数据,将以上sql在该数据库运行。
Quartz 集群实践
引入和配置
1、引入
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.20</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.6</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
2、在对应的application.yml中增加Quartz配置
spring:
quartz:
properties:
org:
quartz:
dataSource:
qzDS:
#数据库连接
URL: jdbc:mysql://127.0.0.1:3306/quartz-config?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
#数据库连接池,将其设置为druid
connectionProvider:
class: com.starnet.aec.model.quartz.config.DruidConnectionProvider
#数据库引擎
driver: com.mysql.cj.jdbc.Driver
#允许最大连接
maxConnection: 10
#数据库密码
password: zhkjmysql@002396
#数据库用户
user: root
#验证查询sql,可以不设置
validationQuery: select 0 from dual
# 10秒后延迟启动 这个很重要,必须要有足够长的时间让你的应用先启动完成后再让 Scheduler启动,
startupDelay: 10s
#持久化方式配置
jobStore:
#数据保存方式为数据库持久化
class: org.quartz.impl.jdbcjobstore.JobStoreTX
#调度实例失效的检查时间间隔,单位毫秒
clusterCheckinInterval: 2000
#数据库别名 随便取
dataSource: qzDS
#数据库代理类,一般org.quartz.impl.jdbcjobstore.StdJDBCDelegate可以满足大部分数据库
driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#是否以集群方式运行
isClustered: true
#最大能忍受的触发超时时间
misfireThreshold: 60000
#数据表的前缀,默认QRTZ_
tablePrefix: QRTZ_
#JobDataMaps是否都为String类型
useProperties: true
#调度配置
scheduler:
#调度器实例编号自动生成
instanceId: AUTO
#调度器实例名称,不同微服务用不同的名字,已达到微服务隔离
instanceName: ${spring.application.name}
#是否在Quartz执行一个job前使用UserTransaction
wrapJobExecutionInUserTransaction: false
#线程池配置
threadPool:
#线程池的实现类
class: org.quartz.simpl.SimpleThreadPool
#线程池中的线程数量
threadCount: 10
#线程优先级
threadPriority: 5
#配置是否启动自动加载数据库内的定时任务,默认true
threadsInheritContextClassLoaderOfInitializingThread: true
3、重新设置 Quartz 数据连接池
默认 Quartz 的数据连接池是 c3p0,由于性能不太稳定,不推荐使用,因此我们将其改成driud
数据连接池,配置如下(这个类全类名要与配置中的connectionProvider
一致):
public class DruidConnectionProvider implements ConnectionProvider {
/**
* 常量配置,与quartz.properties文件的key保持一致(去掉前缀),同时提供set方法,Quartz框架自动注入值。
* @return
* @throws SQLException
*/
//JDBC驱动
public String driver;
//JDBC连接串
public String URL;
//数据库用户名
public String user;
//数据库用户密码
public String password;
//数据库最大连接数
public int maxConnection;
//数据库SQL查询每次连接返回执行到连接池,以确保它仍然是有效的。
public String validationQuery;
private boolean validateOnCheckout;
private int idleConnectionValidationSeconds;
public String maxCachedStatementsPerConnection;
private String discardIdleConnectionsSeconds;
public static final int DEFAULT_DB_MAX_CONNECTIONS = 10;
public static final int DEFAULT_DB_MAX_CACHED_STATEMENTS_PER_CONNECTION = 120;
//Druid连接池
private DruidDataSource datasource;
@Override
public Connection getConnection() throws SQLException {
return datasource.getConnection();
}
@Override
public void shutdown() throws SQLException {
datasource.close();
}
@Override
public void initialize() throws SQLException {
if (this.URL == null) {
throw new SQLException("DBPool could not be created: DB URL cannot be null");
}
if (this.driver == null) {
throw new SQLException("DBPool driver could not be created: DB driver class name cannot be null!");
}
if (this.maxConnection < 0) {
throw new SQLException("DBPool maxConnectins could not be created: Max connections must be greater than zero!");
}
datasource = new DruidDataSource();
try{
datasource.setDriverClassName(this.driver);
} catch (Exception e) {
try {
throw new SchedulerException("Problem setting driver class name on datasource: " + e.getMessage(), e);
} catch (SchedulerException e1) {
}
}
datasource.setUrl(this.URL);
datasource.setUsername(this.user);
datasource.setPassword(this.password);
datasource.setMaxActive(this.maxConnection);
datasource.setMinIdle(1);
datasource.setMaxWait(0);
datasource.setMaxPoolPreparedStatementPerConnectionSize(DEFAULT_DB_MAX_CONNECTIONS);
if (this.validationQuery != null) {
datasource.setValidationQuery(this.validationQuery);
if(!this.validateOnCheckout)
datasource.setTestOnReturn(true);
else
datasource.setTestOnBorrow(true);
datasource.setValidationQueryTimeout(this.idleConnectionValidationSeconds);
}
}
// 省略了get set
}
基础服务编写
1、编写基础QuartzTaskJob
以后要开发Quartz的定时任务继承这个类就好了
public abstract class QuartzTaskJob implements Job {
// 每interval秒执行一次
private Integer interval;
// 定时任务执行的cron表达式
private String cronExp;
// 额外参数,这个可以在定时任务中读取到的
private Map<String, Object> param;
private Logger log = LoggerFactory.getLogger(this.getClass());
public final static String CRON_PER_SECONDS_EXEC = "0/%s * * * * ?";
// 默认的间隔时间为10s
private final static int DEFAULT_INTERVAL = 10;
private final static String CRON_DEFAULT_INTERVAL = "0/10 * * * * ?";
public QuartzTaskJob() {
}
public abstract void taskHandle(JobExecutionContext context);
public String getQuartzCron() {
// interval有值,就用interval的值
if (interval != null) {
return String.format(CRON_PER_SECONDS_EXEC, interval);
}
// interval没值,cronExp有值,就用cronExp
if (cronExp != null) {
return cronExp;
}
// 都没有值,用默认的每10s执行一次
return CRON_DEFAULT_INTERVAL;
}
@Override
public void execute(JobExecutionContext context) {
log.debug("start task 【{}】", this.getClass().toString());
try {
taskHandle(context);
} catch (Exception e) {
log.error(e.getMessage());
} finally {
log.debug("end task 【{}】", this.getClass().toString());
}
}
// 省略的get、set
}
2、编写和实现 Quartz 服务层接口
接口
public interface QuartzJobService {
/**
* 添加任务可以传参数
*/
void addJob(QuartzTaskJob quartzTaskJob);
/**
* 暂停任务
*/
void pauseJob(QuartzTaskJob quartzTaskJob);
/**
* 恢复任务
*/
void resumeJob(QuartzTaskJob quartzTaskJob);
/**
* 立即运行一次定时任务
*/
void runOnce(QuartzTaskJob quartzTaskJob);
/**
* 更新任务
*/
void updateJob(QuartzTaskJob quartzTaskJob);
/**
* 删除任务
*/
void deleteJob(QuartzTaskJob quartzTaskJob);
void deleteJob(String jobName, String groupName);
/**
* 启动所有任务
*/
void startAllJobs();
/**
* 暂停所有任务
*/
void pauseAllJobs();
/**
* 恢复所有任务
*/
void resumeAllJobs();
/**
* 关闭所有任务
*/
void shutdownAllJobs();
}
实现
@Service
public class QuartzJobServiceImpl implements QuartzJobService {
private static final Logger log = LoggerFactory.getLogger(QuartzJobServiceImpl.class);
@Autowired
Scheduler scheduler;
@Value("${spring.application.name}")
String applicationName;
@Override
public void addJob(QuartzTaskJob quartzTaskJob) {
addJob(quartzTaskJob.getClass().getName(),
quartzTaskJob.getClass().getName(),
applicationName, quartzTaskJob.getQuartzCron(),
quartzTaskJob.getParam());
}
private void addJob(String clazzName, String jobName, String groupName, String cronExp, Map<String, Object> param) {
try {
// 生成job key
JobKey jobKey = JobKey.jobKey(jobName, groupName);
if (scheduler.checkExists(jobKey)) {
log.info("job {}, group {}, className {} has exist", jobName, groupName, clazzName);
return;
}
// 启动调度器,默认初始化的时候已经启动
// scheduler.start();
//构建job信息
Class<? extends Job> jobClass = (Class<? extends Job>) Class.forName(clazzName);
JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(jobName, groupName).build();
//表达式调度构建器(即任务执行的时间)
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExp);
//按新的cronExpression表达式构建一个新的trigger
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(jobName, groupName).withSchedule(scheduleBuilder).build();
//获得JobDataMap,写入数据
if (param != null) {
trigger.getJobDataMap().putAll(param);
}
scheduler.scheduleJob(jobDetail, trigger);
} catch (Exception e) {
log.error("创建任务失败", e);
}
}
@Override
public void pauseJob(QuartzTaskJob quartzTaskJob) {
pauseJob(quartzTaskJob.getClass().getName(), applicationName);
}
private void pauseJob(String jobName, String groupName) {
try {
scheduler.pauseJob(JobKey.jobKey(jobName, groupName));
} catch (SchedulerException e) {
log.error("暂停任务失败", e);
}
}
@Override
public void resumeJob(QuartzTaskJob quartzTaskJob) {
resumeJob(quartzTaskJob.getClass().getName(), applicationName);
}
private void resumeJob(String jobName, String groupName) {
try {
scheduler.resumeJob(JobKey.jobKey(jobName, groupName));
} catch (SchedulerException e) {
log.error("恢复任务失败", e);
}
}
@Override
public void runOnce(QuartzTaskJob quartzTaskJob) {
runOnce(quartzTaskJob.getClass().getName(), applicationName);
}
private void runOnce(String jobName, String groupName) {
try {
scheduler.triggerJob(JobKey.jobKey(jobName, groupName));
} catch (SchedulerException e) {
log.error("立即运行一次定时任务失败", e);
}
}
@Override
public void updateJob(QuartzTaskJob quartzTaskJob) {
updateJob(quartzTaskJob.getClass().getName(), applicationName, quartzTaskJob.getQuartzCron(), quartzTaskJob.getParam());
}
private void updateJob(String jobName, String groupName, String cronExp, Map<String, Object> param) {
try {
TriggerKey triggerKey = TriggerKey.triggerKey(jobName, groupName);
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
if (cronExp != null) {
// 表达式调度构建器
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExp);
// 按新的cronExpression表达式重新构建trigger
trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
}
//修改map
if (param != null) {
trigger.getJobDataMap().putAll(param);
}
// 按新的trigger重新设置job执行
scheduler.rescheduleJob(triggerKey, trigger);
} catch (Exception e) {
log.error("更新任务失败", e);
}
}
@Override
public void deleteJob(QuartzTaskJob quartzTaskJob) {
deleteJob(quartzTaskJob.getClass().getName(), applicationName);
}
@Override
public void deleteJob(String jobName, String groupName) {
try {
// 生成job key
JobKey jobKey = JobKey.jobKey(jobName, groupName);
if (!scheduler.checkExists(jobKey)) {
log.info("job {}, group {} not exist, so exit delete job handle", jobName, groupName);
return;
}
// 暂停、移除、删除
scheduler.pauseTrigger(TriggerKey.triggerKey(jobName, groupName));
scheduler.unscheduleJob(TriggerKey.triggerKey(jobName, groupName));
scheduler.deleteJob(JobKey.jobKey(jobName, groupName));
} catch (Exception e) {
log.error("删除任务失败", e);
}
}
@Override
public void startAllJobs() {
try {
scheduler.start();
} catch (Exception e) {
log.error("开启所有的任务失败", e);
}
}
@Override
public void pauseAllJobs() {
try {
scheduler.pauseAll();
} catch (Exception e) {
log.error("暂停所有任务失败", e);
}
}
@Override
public void resumeAllJobs() {
try {
scheduler.resumeAll();
} catch (Exception e) {
log.error("恢复所有任务失败", e);
}
}
@Override
public void shutdownAllJobs() {
try {
if (!scheduler.isShutdown()) {
// 需谨慎操作关闭scheduler容器
// scheduler生命周期结束,无法再 start() 启动scheduler
scheduler.shutdown(true);
}
} catch (Exception e) {
log.error("关闭所有的任务失败", e);
}
}
}
基础测试
1、编写测试Quartz定时任务
// 需要实现抽象类QuartzTaskJob,在抽象方法taskHandle中编写定时任务需要执行的代码
// 因为使用的是Spring.Boot2.x因此,可以直接使用@Autowired进行Bean的注入和使用
public class TestQuartJobTask extends QuartzTaskJob {
@Override
public void taskHandle(JobExecutionContext context) {
log.info("test quartz job task, exec time {}", new Date());
}
}
2、将新增定时任务到Quartz中
@Autowired
QuartzJobService quartzJobService;
@PostConstruct
public void init() {
TestQuartJobTask quartzJobTask = new TestQuartJobTask();
// 设置每多少秒执行一次
// quartzJobTask.setInterval(10);
// 设置定时执行的cron表达式
// quartzJobTask.setCronExp("0/10 * * * * ?");
// 如果都没有设置默认是每10秒执行一次!!!
// 如果都设置了,以Interval为准!!!!!
quartzJobService.addJob(quartzJobTask);
}
3、启动两个启用了Quartz定时任务的同名微服务
两个微服务(A1和A2)都会注册到Quartz调度中心中,先注册的微服务A1会进行定时任务的执行;
若此时将微服务A1关掉,那么微服务A2会接替A1进行定时任务的执行;
将A1重新开启,微服务A2还是继续执行定时任务;
将微服务A2关掉之后,微服务A1会接替进行定时任务的执行。
4、再启动一个启用了Quartz定时任务的不同名的微服务
微服务B与微服务A1和A2是不同名的微服务,微服务B启动之后会执行自己的Quartz定时任务,而微服务A1和A2也会根据3中规则执行自己的定时任务;
达到分布式微服务同一任务、同一时间只会有一个微服务执行,且不同微服务互相隔离的效果。
附录
Cron表达式
Cron表达式 是用来配置 CronTrigger 实例,它是一个由 7 个子表达式组成的字符串,每个字符都表示不同的日程细节:
Seconds
:秒Minutes
:分钟Hours
:小时Day-of-Month
:月中的哪几天Month
:月Day-of-Week
:周中的哪几天Year
:年
字段 | 是否必填 | 允许值 | 可用特殊字符 |
---|---|---|---|
秒 | 是 | 0-59 | , - * / |
分 | 是 | 0-59 | , - * / |
小时 | 是 | 0-23 | , - * / |
月中的哪几天 | 是 | 1-31 | , - * / ? L W C |
月 | 是 | 1-12 或 JAN-DEC | , - * / |
周中的哪几天 | 是 | 1-7 或 SUN-SAT | , - * / ? L C # |
年 | 否 | 不填写 或 1970-2099 | , - * / |
- 特殊符号
特殊符号 | 含义 |
---|---|
* | 可用在所有字段中,表示对应时间域的每一个时刻,例如,***** 在分钟字段时,表示“每分钟” |
? | 该字符只在日期和星期字段中使用,它通常指定为“无意义的值”,相当于点位符 |
- | 表达一个范围,如在小时字段中使用“10-12”,则表示从10到12点,即10,11,12 |
, | 表达一个列表值,如在星期字段中使用“MON,WED,FRI”,则表示星期一,星期三和星期五 |
/ | x/y表达一个等步长序列,x为起始值,y为增量步长值。如在分钟字段中使用0/15,则表示为0,15,30和45秒,而5/15在分钟字段中表示5,20,35,50,你也可以使用*/y,它等同于0/y |
L | 该字符只在日期和星期字段中使用,代表“Last”的意思,但它在两个字段中意思不同。L在日期字段中,表示这个月份的最后一天,如一月的31号,非闰年二月的28号;如果L用在星期中,则表示星期六,等同于7。但是,如果L出现在星期字段里,而且在前面有一个数值 X,则表示“这个月的最后X天”,例如,6L表示该月的最后星期五 |
W | 该字符只能出现在日期字段里,是对前导日期的修饰,表示离该日期最近的工作日。例如15W表示离该月15号最近的工作日,如果该月15号是星期六,则匹配14号星期五;如果15日是星期日,则匹配16号星期一;如果15号是星期二,那结果就是15号星期二。但必须注意关联的匹配日期不能够跨月,如你指定1W,如果1号是星期六,结果匹配的是3号星期一,而非上个月最后的那天。W字符串只能指定单一日期,而不能指定日期范围 |
# | 该字符只能在星期字段中使用,表示当月某个工作日。如6#3表示当月的第三个星期五(6表示星期五,#3表示当前的第三个),而4#5表示当月的第五个星期三,假设当月没有第五个星期三,忽略不触发 |
Cron表达式对特殊字符的大小写不敏感,对代表星期的缩写英文大小写也不敏感。
更多推荐
所有评论(0)