上次自己搭建Quartz已經是幾年前的事了,這次項目中需要定時任務,需要支持集群部署,想到比較輕量級的定時任務框架就是Quartz,於是來一波。
版本說明
通過搜索引擎很容易找到其官網,來到Document的頁面,當前版本是2.2.x。
簡單的搭建操作
通過Maven引入所需的包:
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.2.3</version>
</dependency>
通過quartz.properties設置相關配置:
# 線程調度器實例名
org.quartz.scheduler.instanceName = quartzScheduler
# 線程池的線程數,即最多3個任務同時跑
org.quartz.threadPool.threadCount = 3
# 使用內存存儲任務和觸發器等信息
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
定義任務,如MySimpleJob,再初始化Scheduler,最后將任務和出發器注冊到Scheduler上:
package com.nicchagil.exercise.quartz.springbootquartzexercise;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.quartz.SimpleScheduleBuilder.simpleSchedule;
public class QuartzBoot {
private static Logger logger = LoggerFactory.getLogger(QuartzBoot.class);
public static void main(String[] args) {
try {
// 獲取調度器
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
// 開啟調度器
scheduler.start();
// 注冊一個示例任務和觸發器
registerJobAndTrigger(scheduler);
// scheduler.shutdown();
} catch (SchedulerException se) {
logger.error("調度器初始化異常", se);
}
}
/**
* 注冊一個任務和觸發器
*/
public static void registerJobAndTrigger(Scheduler scheduler) {
JobDetail job = JobBuilder.newJob(MySimpleJob.class)
.withIdentity("mySimpleJob", "simpleGroup")
.build();
Trigger trigger = org.quartz.TriggerBuilder.newTrigger()
.withIdentity("simpleTrigger", "simpleGroup")
.startNow()
.withSchedule(simpleSchedule()
.withIntervalInSeconds(10)
.repeatForever())
.build();
try {
scheduler.scheduleJob(job, trigger);
} catch (SchedulerException e) {
logger.error("注冊任務和觸發器失敗", e);
}
}
/**
* 簡單的任務
*/
public static class MySimpleJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
logger.info("哇真的執行了");
}
}
}
啟動日志,你可以看到任務按照指定的觸發器跑:
13:31:28.759 [main] INFO org.quartz.impl.StdSchedulerFactory - Using default implementation for ThreadExecutor
13:31:28.828 [main] INFO org.quartz.core.SchedulerSignalerImpl - Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl
13:31:28.828 [main] INFO org.quartz.core.QuartzScheduler - Quartz Scheduler v.2.2.3 created.
13:31:28.831 [main] INFO org.quartz.simpl.RAMJobStore - RAMJobStore initialized.
13:31:28.833 [main] INFO org.quartz.core.QuartzScheduler - Scheduler meta-data: Quartz Scheduler (v2.2.3) 'quartzScheduler' with instanceId 'NON_CLUSTERED'
Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.
NOT STARTED.
Currently in standby mode.
Number of jobs executed: 0
Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 3 threads.
Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered.
13:31:28.833 [main] INFO org.quartz.impl.StdSchedulerFactory - Quartz scheduler 'quartzScheduler' initialized from default resource file in Quartz package: 'quartz.properties'
13:31:28.833 [main] INFO org.quartz.impl.StdSchedulerFactory - Quartz scheduler version: 2.2.3
13:31:28.834 [main] INFO org.quartz.core.QuartzScheduler - Scheduler quartzScheduler_$_NON_CLUSTERED started.
13:31:28.834 [quartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 0 triggers
13:31:28.853 [quartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers
13:31:28.860 [quartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.simpl.PropertySettingJobFactory - Producing instance of Job 'simpleGroup.mySimpleJob', class=com.nicchagil.exercise.quartz.springbootquartzexercise.QuartzBoot$MySimpleJob
13:31:28.869 [quartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers
13:31:28.871 [quartzScheduler_Worker-1] DEBUG org.quartz.core.JobRunShell - Calling execute on job simpleGroup.mySimpleJob
13:31:28.871 [quartzScheduler_Worker-1] INFO com.nicchagil.exercise.quartz.springbootquartzexercise.QuartzBoot - 哇真的執行了
13:31:38.842 [quartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.simpl.PropertySettingJobFactory - Producing instance of Job 'simpleGroup.mySimpleJob', class=com.nicchagil.exercise.quartz.springbootquartzexercise.QuartzBoot$MySimpleJob
13:31:38.842 [quartzScheduler_Worker-2] DEBUG org.quartz.core.JobRunShell - Calling execute on job simpleGroup.mySimpleJob
13:31:38.842 [quartzScheduler_Worker-2] INFO com.nicchagil.exercise.quartz.springbootquartzexercise.QuartzBoot - 哇真的執行了
擴展
查看StdSchedulerFactory.getDefaultScheduler()
,會發現用的是new StdSchedulerFactory()
,在initialize()
方法可以看到默認從指定配置的文件或quartz.properties
讀取配置:
String requestedFile = System.getProperty("org.quartz.properties");
String propFileName = requestedFile != null ? requestedFile : "quartz.properties";
調度器待機狀態、關閉狀態
在調用scheduler.start()
啟動調度器后,可以使用scheduler.standby();
將調度器轉為待機狀態,此狀態下任務和觸發器不會被觸發。
另外,可以使用scheduler.shutdown()
關閉調度器,是不可逆的,即調用后是不可以重新開始的。
它的參數不同,意義有所不同:
scheduler.shutdown()
=scheduler.shutdown(false)
,方法會馬上返回,正在執行的任務會繼續執行scheduler.shutdown(true)
,知道正在執行的任務執行完成才返回
在getScheduler()
可以看到使用SchedulerRepository
緩存了Scheduler
,使用的是HashMap<String, Scheduler>
:
SchedulerRepository schedRep = SchedulerRepository.getInstance();
Scheduler sched = schedRep.lookup(this.getSchedulerName());
與Spring Boot集成
首先,quartz.properties與之前的基本一致:
# 線程調度器實例名
org.quartz.scheduler.instanceName = quartzScheduler
# 線程池的線程數,即最多3個任務同時跑
org.quartz.threadPool.threadCount = 3
# 使用內存存儲任務和觸發器等信息
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
因會與Spring Scheduling集成,我們按照要求定義任務,需要特別注意的是@Component
和@EnableScheduling
:
package com.nicchagil.exercise.quartz.springbootquartzexercise.component.quartz.job;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.stereotype.Component;
@Component
@EnableScheduling
public class MyFirstExerciseJob {
private Logger logger = LoggerFactory.getLogger(this.getClass());
public void myJobBusinessMethod() {
this.logger.info("哇被觸發了哈哈哈哈哈");
}
}
類似的方式,可以定義MySecondExerciseJob
。
注冊任務和觸發器:
package com.nicchagil.exercise.quartz.springbootquartzexercise.component.quartz;
import com.nicchagil.exercise.quartz.springbootquartzexercise.component.quartz.job.MyFirstExerciseJob;
import com.nicchagil.exercise.quartz.springbootquartzexercise.component.quartz.job.MySecondExerciseJob;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.CronTriggerFactoryBean;
import org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean;
@Configuration
public class QuartzJobConfig {
/**
* 方法調用任務明細工廠Bean
*/
@Bean(name = "myFirstExerciseJobBean")
public MethodInvokingJobDetailFactoryBean myFirstExerciseJobBean(MyFirstExerciseJob myFirstExerciseJob) {
MethodInvokingJobDetailFactoryBean jobDetail = new MethodInvokingJobDetailFactoryBean();
jobDetail.setConcurrent(false); // 是否並發
jobDetail.setName("general-myFirstExerciseJob"); // 任務的名字
jobDetail.setGroup("general"); // 任務的分組
jobDetail.setTargetObject(myFirstExerciseJob); // 被執行的對象
jobDetail.setTargetMethod("myJobBusinessMethod"); // 被執行的方法
return jobDetail;
}
/**
* 表達式觸發器工廠Bean
*/
@Bean(name = "myFirstExerciseJobTrigger")
public CronTriggerFactoryBean myFirstExerciseJobTrigger(@Qualifier("myFirstExerciseJobBean") MethodInvokingJobDetailFactoryBean myFirstExerciseJobBean) {
CronTriggerFactoryBean tigger = new CronTriggerFactoryBean();
tigger.setJobDetail(myFirstExerciseJobBean.getObject());
tigger.setCronExpression("0/10 * * * * ?"); // 什么是否觸發,Spring Scheduler Cron表達式
tigger.setName("general-myFirstExerciseJobTrigger");
return tigger;
}
/**
* 方法調用任務明細工廠Bean
*/
@Bean(name = "mySecondExerciseJobBean")
public MethodInvokingJobDetailFactoryBean mySecondExerciseJobBean(MySecondExerciseJob mySecondExerciseJob) {
MethodInvokingJobDetailFactoryBean jobDetail = new MethodInvokingJobDetailFactoryBean();
jobDetail.setConcurrent(false); // 是否並發
jobDetail.setName("general-mySecondExerciseJob"); // 任務的名字
jobDetail.setGroup("general"); // 任務的分組
jobDetail.setTargetObject(mySecondExerciseJob); // 被執行的對象
jobDetail.setTargetMethod("myJobBusinessMethod"); // 被執行的方法
return jobDetail;
}
/**
* 表達式觸發器工廠Bean
*/
@Bean(name = "mySecondExerciseJobTrigger")
public CronTriggerFactoryBean mySecondExerciseJobTrigger(@Qualifier("mySecondExerciseJobBean") MethodInvokingJobDetailFactoryBean mySecondExerciseJobDetailFactoryBean) {
CronTriggerFactoryBean tigger = new CronTriggerFactoryBean();
tigger.setJobDetail(mySecondExerciseJobDetailFactoryBean.getObject());
tigger.setCronExpression("0/10 * * * * ?"); // 什么是否觸發,Spring Scheduler Cron表達式
tigger.setName("general-mySecondExerciseJobTrigger");
return tigger;
}
}
將任務和觸發器注冊到調度器:
package com.nicchagil.exercise.quartz.springbootquartzexercise.component.quartz;
import org.quartz.Trigger;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
@Configuration
public class QuartzConfig {
/**
* 調度器工廠Bean
*/
@Bean(name = "schedulerFactory")
public SchedulerFactoryBean schedulerFactory(@Qualifier("myFirstExerciseJobTrigger") Trigger myFirstExerciseJobTrigger,
@Qualifier("mySecondExerciseJobTrigger") Trigger mySecondExerciseJobTrigger) {
SchedulerFactoryBean bean = new SchedulerFactoryBean();
// 覆蓋已存在的任務
bean.setOverwriteExistingJobs(true);
// 延時啟動定時任務,避免系統未完全啟動卻開始執行定時任務的情況
bean.setStartupDelay(15);
// 注冊觸發器
bean.setTriggers(myFirstExerciseJobTrigger, mySecondExerciseJobTrigger);
return bean;
}
}
持久化
任務持久化需要用到數據庫,而初始化數據庫的SQL可以從下載的發布版的文件中找到,比如,我在官網的Download頁下載了當前版本的Full Distribution:Quartz 2.2.3 .tar.gz
,解壓后在quartz-2.2.3\docs\dbTables
能找到初始化腳本,因我用的是MySQL的Innodb引擎,所以我用此腳本tables_mysql_innodb.sql
。
不能持久化的問題
如果使用的是MethodInvokingJobDetailFactoryBean
,持久化會有如下報錯:Couldn't store job: Unable to serialize JobDataMap for insertion into database because the value of
,我們切換使用JobDetailFactoryBean
。
簡單的持久化示例
quartz.properties的配置有所不同,比如會使用JobStoreTX,然后會指定數據源的信息:
# 線程調度器實例名
org.quartz.scheduler.instanceName = quartzScheduler
# 線程池的線程數,即最多3個任務同時跑
org.quartz.threadPool.threadCount = 3
# 如何存儲任務和觸發器等信息
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
# 驅動代理
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
# 表前綴
org.quartz.jobStore.tablePrefix = qrtz_
# 數據源
org.quartz.jobStore.dataSource = quartzDataSource
# 是否集群
org.quartz.jobStore.isClustered = false
# 數據源
# 驅動
org.quartz.dataSource.quartzDataSource.driver = com.mysql.cj.jdbc.Driver
# 連接URL
org.quartz.dataSource.quartzDataSource.URL = jdbc:mysql://localhost:3306/quartz?characterEncoding=utf-8&useSSL=true&&serverTimezone=Asia/Shanghai
# 用戶名
org.quartz.dataSource.quartzDataSource.user = root
# 密碼
org.quartz.dataSource.quartzDataSource.password = 123456
# 最大連接數
org.quartz.dataSource.quartzDataSource.maxConnections = 5
定義的任務有所不同:
package com.nicchagil.exercise.quartz.springbootquartzexercise.component.quartz.job;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.stereotype.Component;
@Component
@EnableScheduling
public class MyFirstExerciseJob implements Job {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public void execute(JobExecutionContext jobExecutionContext) {
this.myJobBusinessMethod();
}
public void myJobBusinessMethod() {
this.logger.info("哇被觸發了哈哈哈哈哈");
}
}
注冊任務和觸發器的方式有所不同:
/**
* 方法調用任務明細工廠Bean
*/
@Bean(name = "myFirstExerciseJobBean")
public JobDetailFactoryBean myFirstExerciseJobBean() {
JobDetailFactoryBean jobDetail = new JobDetailFactoryBean();
jobDetail.setName("general-myFirstExerciseJob"); // 任務的名字
jobDetail.setGroup("general"); // 任務的分組
jobDetail.setJobClass(MyFirstExerciseJob.class);
jobDetail.setDurability(true);
return jobDetail;
}
/**
* 表達式觸發器工廠Bean
*/
@Bean(name = "myFirstExerciseJobTrigger")
public CronTriggerFactoryBean myFirstExerciseJobTrigger(@Qualifier("myFirstExerciseJobBean") JobDetailFactoryBean myFirstExerciseJobBean) {
CronTriggerFactoryBean tigger = new CronTriggerFactoryBean();
tigger.setJobDetail(myFirstExerciseJobBean.getObject());
tigger.setCronExpression("0/10 * * * * ?"); // 什么是否觸發,Spring Scheduler Cron表達式
tigger.setName("general-myFirstExerciseJobTrigger");
return tigger;
}
注冊所有任務和觸發器:
package com.nicchagil.exercise.quartz.springbootquartzexercise.component.quartz;
import org.quartz.Trigger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import javax.sql.DataSource;
import java.io.IOException;
import java.util.Properties;
@Configuration
public class QuartzConfig {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
@Qualifier(value = "primaryDataSource")
private DataSource primaryDataSource;
/**
* 調度器工廠Bean
*/
@Bean(name = "schedulerFactory")
public SchedulerFactoryBean schedulerFactory( Trigger... triggers) {
SchedulerFactoryBean bean = new SchedulerFactoryBean();
Properties p = new Properties();
try {
p.load(this.getClass().getClassLoader().getResourceAsStream("quartz.properties"));
} catch (IOException e) {
this.logger.error("加載quartz.properties失敗", e);
throw new Error(e);
}
bean.setQuartzProperties(p);
// 覆蓋已存在的任務
bean.setOverwriteExistingJobs(true);
// 延時啟動定時任務,避免系統未完全啟動卻開始執行定時任務的情況
bean.setStartupDelay(15);
// 注冊觸發器
bean.setTriggers(triggers);
return bean;
}
}
使用應用的數據源
有時候持久化的數據源用的是應用的數據源,可以使用bean.setDataSource(dataSource)
設置或覆蓋數據源。
比如,應用的數據源是這樣的。
POM.XML:
<!-- 引入數據庫連接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.6</version>
</dependency>
<!-- Spring Boot JDBC -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
數據源配置:
spring.dataSource.primaryDataSource.type = com.alibaba.druid.pool.DruidDataSource
spring.dataSource.primaryDataSource.url = jdbc:mysql://localhost:33306/quartz?characterEncoding=utf-8&useSSL=true&&serverTimezone=Asia/Shanghai&allowMultiQueries=true&autoReconnect=true
spring.dataSource.primaryDataSource.username = root
spring.dataSource.primaryDataSource.password = 123456
spring.dataSource.primaryDataSource.driverClassName = com.mysql.jdbc.Driver
spring.dataSource.primaryDataSource.initialSize = 5
spring.dataSource.primaryDataSource.minIdle = 5
spring.dataSource.primaryDataSource.maxActive = 15
spring.dataSource.primaryDataSource.maxWait = 60000
spring.dataSource.primaryDataSource.timeBetweenEvictionRunsMillis = 60000
spring.dataSource.primaryDataSource.minEvictableIdleTimeMillis = 300000
spring.dataSource.primaryDataSource.validationQuery = SELECT 1 FROM DUAL
spring.dataSource.primaryDataSource.testWhileIdle = true
spring.dataSource.primaryDataSource.testOnBorrow = true
spring.dataSource.primaryDataSource.testOnReturn = true
配置載入類:
package com.nicchagil.exercise.quartz.springbootquartzexercise.component.datasourcepool;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "spring.dataSource.primaryDataSource")
public class DruidPrimaryDataSourceConfigProperties {
private String type;
private String url;
private String username;
private String password;
private String driverClassName;
private Integer initialSize;
private Integer minIdle;
private Integer maxActive;
private Integer maxWait;
private Integer timeBetweenEvictionRunsMillis;
private Integer minEvictableIdleTimeMillis;
private String validationQuery;
private Boolean testWhileIdle;
private Boolean testOnBorrow;
private Boolean testOnReturn;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getDriverClassName() {
return driverClassName;
}
public void setDriverClassName(String driverClassName) {
this.driverClassName = driverClassName;
}
public Integer getInitialSize() {
return initialSize;
}
public void setInitialSize(Integer initialSize) {
this.initialSize = initialSize;
}
public Integer getMinIdle() {
return minIdle;
}
public void setMinIdle(Integer minIdle) {
this.minIdle = minIdle;
}
public Integer getMaxActive() {
return maxActive;
}
public void setMaxActive(Integer maxActive) {
this.maxActive = maxActive;
}
public Integer getMaxWait() {
return maxWait;
}
public void setMaxWait(Integer maxWait) {
this.maxWait = maxWait;
}
public Integer getTimeBetweenEvictionRunsMillis() {
return timeBetweenEvictionRunsMillis;
}
public void setTimeBetweenEvictionRunsMillis(Integer timeBetweenEvictionRunsMillis) {
this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis;
}
public Integer getMinEvictableIdleTimeMillis() {
return minEvictableIdleTimeMillis;
}
public void setMinEvictableIdleTimeMillis(Integer minEvictableIdleTimeMillis) {
this.minEvictableIdleTimeMillis = minEvictableIdleTimeMillis;
}
public String getValidationQuery() {
return validationQuery;
}
public void setValidationQuery(String validationQuery) {
this.validationQuery = validationQuery;
}
public Boolean getTestWhileIdle() {
return testWhileIdle;
}
public void setTestWhileIdle(Boolean testWhileIdle) {
this.testWhileIdle = testWhileIdle;
}
public Boolean getTestOnBorrow() {
return testOnBorrow;
}
public void setTestOnBorrow(Boolean testOnBorrow) {
this.testOnBorrow = testOnBorrow;
}
public Boolean getTestOnReturn() {
return testOnReturn;
}
public void setTestOnReturn(Boolean testOnReturn) {
this.testOnReturn = testOnReturn;
}
}
初始化數據源:
package com.nicchagil.exercise.quartz.springbootquartzexercise.component.datasourcepool;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
@Configuration
public class DruidPrimaryDataSourceConfig {
@Autowired
private DruidPrimaryDataSourceConfigProperties druidPrimaryDataSourceConfigProperties;
@Bean
public DataSource primaryDataSource (){
DruidDataSource datasource = new DruidDataSource();
/* 基礎配置 */
datasource.setUrl(this.druidPrimaryDataSourceConfigProperties.getUrl());
datasource.setUsername(this.druidPrimaryDataSourceConfigProperties.getUsername());
datasource.setPassword(this.druidPrimaryDataSourceConfigProperties.getPassword());
datasource.setDriverClassName(this.druidPrimaryDataSourceConfigProperties.getDriverClassName());
/* 其他配置 */
datasource.setInitialSize(this.druidPrimaryDataSourceConfigProperties.getInitialSize());
datasource.setMinIdle(this.druidPrimaryDataSourceConfigProperties.getMinIdle());
datasource.setMaxActive(this.druidPrimaryDataSourceConfigProperties.getMaxActive());
datasource.setMaxWait(this.druidPrimaryDataSourceConfigProperties.getMaxWait());
datasource.setTimeBetweenEvictionRunsMillis(this.druidPrimaryDataSourceConfigProperties.getTimeBetweenEvictionRunsMillis());
datasource.setMinEvictableIdleTimeMillis(this.druidPrimaryDataSourceConfigProperties.getMinEvictableIdleTimeMillis());
datasource.setValidationQuery(this.druidPrimaryDataSourceConfigProperties.getValidationQuery());
datasource.setTestWhileIdle(this.druidPrimaryDataSourceConfigProperties.getTestWhileIdle());
datasource.setTestOnBorrow(this.druidPrimaryDataSourceConfigProperties.getTestOnBorrow());
datasource.setTestOnReturn(this.druidPrimaryDataSourceConfigProperties.getTestOnReturn());
return datasource;
}
}
可以使用bean.setDataSource(dataSource)
設置或覆蓋數據源:
package com.nicchagil.exercise.quartz.springbootquartzexercise.component.quartz;
import org.quartz.Trigger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import javax.sql.DataSource;
import java.io.IOException;
import java.util.Properties;
@Configuration
public class QuartzConfig {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
@Qualifier(value = "primaryDataSource")
private DataSource primaryDataSource;
/**
* 調度器工廠Bean
*/
@Bean(name = "schedulerFactory")
public SchedulerFactoryBean schedulerFactory( Trigger... triggers) {
SchedulerFactoryBean bean = new SchedulerFactoryBean();
Properties p = new Properties();
try {
p.load(this.getClass().getClassLoader().getResourceAsStream("quartz.properties"));
} catch (IOException e) {
this.logger.error("加載quartz.properties失敗", e);
throw new Error(e);
}
bean.setQuartzProperties(p);
/* 使用應用的數據源 */
bean.setDataSource(this.primaryDataSource);
// 覆蓋已存在的任務
bean.setOverwriteExistingJobs(true);
// 延時啟動定時任務,避免系統未完全啟動卻開始執行定時任務的情況
bean.setStartupDelay(15);
// 注冊觸發器
bean.setTriggers(triggers);
return bean;
}
}
數據表查詢
QRTZ_CRON_TRIGGERS,觸發器表
QRTZ_FIRED_TRIGGERS,已觸發的觸發表
QRTZ_JOB_DETAILS,任務明細表
QRTZ_TRIGGERS,觸發器表
QRTZ_LOCKS,鎖表
並發執行
保證上一次任務執行完畢,再執行下一次任務
在任務類上注解@DisallowConcurrentExecution
,比如此任務需耗時7秒,卻配置5秒執行一次,注解后將會7秒才運行一次:
@Component
@EnableScheduling
@DisallowConcurrentExecution // 保證上一次任務執行完畢再執行下一任務
public class MyFirstExerciseJob implements Job {
日志:
2018-02-21 12:09:04.479 INFO 9496 --- [actory_Worker-3] c.n.e.q.s.c.q.job.MyFirstExerciseJob : 哇被觸發了哈哈哈哈哈 x 5
2018-02-21 12:09:11.629 INFO 9496 --- [actory_Worker-2] c.n.e.q.s.c.q.job.MyFirstExerciseJob : 哇被觸發了哈哈哈哈哈 x 6
2018-02-21 12:09:18.796 INFO 9496 --- [actory_Worker-1] c.n.e.q.s.c.q.job.MyFirstExerciseJob : 哇被觸發了哈哈哈哈哈 x 7
2018-02-21 12:09:26.016 INFO 9496 --- [actory_Worker-3] c.n.e.q.s.c.q.job.MyFirstExerciseJob : 哇被觸發了哈哈哈哈哈 x 8
2018-02-21 12:09:33.268 INFO 9496 --- [actory_Worker-2] c.n.e.q.s.c.q.job.MyFirstExerciseJob : 哇被觸發了哈哈哈哈哈 x 9
2018-02-21 12:09:40.518 INFO 9496 --- [actory_Worker-1] c.n.e.q.s.c.q.job.MyFirstExerciseJob : 哇被觸發了哈哈哈哈哈 x 10
2018-02-21 12:09:47.668 INFO 9496 --- [actory_Worker-3] c.n.e.q.s.c.q.job.MyFirstExerciseJob : 哇被觸發了哈哈哈哈哈 x 11
2018-02-21 12:09:54.869 INFO 9496 --- [actory_Worker-2] c.n.e.q.s.c.q.job.MyFirstExerciseJob : 哇被觸發了哈哈哈哈哈 x 12
集群
下面配置展示了Quartz的必要配置:
- instanceName,實例名,集群各節點的實例名相同
- instanceId,實例ID,設為AUTO則由Quartz自動根據主機名、時間戳生成實例ID
- org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX,存儲任務為數據庫存儲,不能使用內存,即
RAMJobStore
- isClustered,告訴Quartz是否為集群模式
# 線程調度器實例名
org.quartz.scheduler.instanceName = QuartzScheduler
# 線程池的線程數,即最多3個任務同時跑
org.quartz.threadPool.threadCount = 3
# 實例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 = qrtz_
# 是否集群
org.quartz.jobStore.isClustered = true
配置后,啟動多個節點,你會發現:
- 一個任務只有一個節點觸發,不會多節點都被觸發
- 當一個節點宕機,其它節點會接手任務的觸發