1. 程式人生 > 其它 >SpringBoot 整合 Quartz 實現分散式排程

SpringBoot 整合 Quartz 實現分散式排程

一、摘要
springboot + quartz + postgres實現持久化分散式排程
叢集環境任務排程測試
二、Quartz 叢集架構
Quartz 是 Java 領域最著名的開源任務排程工具。

在上篇文章中,我們詳細的介紹了 Quartz 的單體應用實踐,如果只在單體環境中應用,Quartz 未必是最好的選擇,例如Spring Scheduled一樣也可以實現任務排程,並且與SpringBoot無縫整合,支援註解配置,非常簡單,但是它有個缺點就是在叢集環境下,會導致任務被重複排程!

而與之對應的 Quartz 提供了極為廣用的特性,如任務持久化、叢集部署和分散式排程任務等等,正因如此,基於 Quartz 任務排程功能在系統開發中應用極為廣泛!

在叢集環境下,Quartz 叢集中的每個節點是一個獨立的 Quartz 應用,沒有負責集中管理的節點,而是通過資料庫表來感知另一個應用,利用資料庫鎖的方式來實現叢集環境下進行併發控制,每個任務當前執行的有效節點有且只有一個!

特別需要注意的是:分散式部署時需要保證各個節點的系統時間一致!

三、資料表初始化
資料庫表結構去官網下載,地址:quartz官網地址

這裡下載2.3.0

選擇對應的資料庫指令碼,我們選擇的是postgres

-- Thanks to Patrick Lightbody for submitting this...
--
-- In your Quartz properties file, you'll need to set
-- org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.PostgreSQLDelegate

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_cron_triggers;
DROP TABLE IF EXISTS qrtz_simprop_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(200) NOT NULL,
JOB_GROUP VARCHAR(200) NOT NULL,
DESCRIPTION VARCHAR(250) NULL,
JOB_CLASS_NAME VARCHAR(250) NOT NULL,
IS_DURABLE BOOL NOT NULL,
IS_NONCONCURRENT BOOL NOT NULL,
IS_UPDATE_DATA BOOL NOT NULL,
REQUESTS_RECOVERY BOOL NOT NULL,
JOB_DATA BYTEA NULL,
PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
);

CREATE TABLE qrtz_triggers
(
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
JOB_NAME VARCHAR(200) NOT NULL,
JOB_GROUP VARCHAR(200) NOT NULL,
DESCRIPTION VARCHAR(250) NULL,
NEXT_FIRE_TIME BIGINT NULL,
PREV_FIRE_TIME BIGINT NULL,
PRIORITY INTEGER NULL,
TRIGGER_STATE VARCHAR(16) NOT NULL,
TRIGGER_TYPE VARCHAR(8) NOT NULL,
START_TIME BIGINT NOT NULL,
END_TIME BIGINT NULL,
CALENDAR_NAME VARCHAR(200) NULL,
MISFIRE_INSTR SMALLINT NULL,
JOB_DATA BYTEA 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)
);

CREATE TABLE qrtz_simple_triggers
(
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
REPEAT_COUNT BIGINT NOT NULL,
REPEAT_INTERVAL BIGINT NOT NULL,
TIMES_TRIGGERED BIGINT 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)
);

CREATE TABLE qrtz_cron_triggers
(
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) 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)
);

CREATE TABLE qrtz_simprop_triggers
(
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) 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 BOOL NULL,
BOOL_PROP_2 BOOL 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)
);

CREATE TABLE qrtz_blob_triggers
(
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
BLOB_DATA BYTEA 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)
);

CREATE TABLE qrtz_calendars
(
SCHED_NAME VARCHAR(120) NOT NULL,
CALENDAR_NAME VARCHAR(200) NOT NULL,
CALENDAR BYTEA NOT NULL,
PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)
);


CREATE TABLE qrtz_paused_trigger_grps
(
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)
);

CREATE TABLE qrtz_fired_triggers
(
SCHED_NAME VARCHAR(120) NOT NULL,
ENTRY_ID VARCHAR(95) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
INSTANCE_NAME VARCHAR(200) NOT NULL,
FIRED_TIME BIGINT NOT NULL,
SCHED_TIME BIGINT NOT NULL,
PRIORITY INTEGER NOT NULL,
STATE VARCHAR(16) NOT NULL,
JOB_NAME VARCHAR(200) NULL,
JOB_GROUP VARCHAR(200) NULL,
IS_NONCONCURRENT BOOL NULL,
REQUESTS_RECOVERY BOOL NULL,
PRIMARY KEY (SCHED_NAME,ENTRY_ID)
);

CREATE TABLE qrtz_scheduler_state
(
SCHED_NAME VARCHAR(120) NOT NULL,
INSTANCE_NAME VARCHAR(200) NOT NULL,
LAST_CHECKIN_TIME BIGINT NOT NULL,
CHECKIN_INTERVAL BIGINT NOT NULL,
PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)
);

CREATE TABLE qrtz_locks
(
SCHED_NAME VARCHAR(120) NOT NULL,
LOCK_NAME VARCHAR(40) NOT NULL,
PRIMARY KEY (SCHED_NAME,LOCK_NAME)
);

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_LOCKS 就是 Quartz 叢集實現同步機制的行鎖表!

四、Quartz springboot程式碼實現

4.1、建立springboot專案,匯入maven依賴包

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
4.2、註冊 Quartz 任務工廠

/**
* @Author haylee
* @Date 2021/4/13 15:50
* @Version 1.0
* @Description 解決job中service注入為空的問題。
*/
@Component
public class SpringJobFactory extends AdaptableJobFactory {

@Autowired
private AutowireCapableBeanFactory capableBeanFactory;

@Override
protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
//呼叫父類的方法
Object jobInstance = super.createJobInstance(bundle);
//進行注入
capableBeanFactory.autowireBean(jobInstance);
return jobInstance;
}
}
4.3、註冊排程工廠
這裡我們使用spring預設的資料來源,網上基本都是獨立資料來源,很少人想到怎麼使用spring資料來源。因為spring資料來源datasource已經被spring容器管理,我們只需要例項SchedulerFactoryBean是注入就可以了。

/**
* @Author haylee
* @Date 2021/4/13 15:50
* @Version 1.0
* @Description TODO
*/
@Configuration
public class QuartzConfig {

/**
* 重寫AdaptableJobFactory,解決service注入為null的問題
*/
@Autowired
private SpringJobFactory springJobFactory;

@Bean
public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource) {
SchedulerFactoryBean factory = new SchedulerFactoryBean();
factory.setDataSource(dataSource);

//quartz引數
Properties prop = new Properties();
prop.put("org.quartz.scheduler.instanceName", "safeScheduler");
prop.put("org.quartz.scheduler.instanceId", "AUTO");
//執行緒池配置
prop.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool");
prop.put("org.quartz.threadPool.threadCount", "20");
prop.put("org.quartz.threadPool.threadPriority", "5");
//JobStore配置
prop.put("org.quartz.jobStore.class", "org.quartz.impl.jdbcjobstore.JobStoreTX");
//叢集配置
prop.put("org.quartz.jobStore.isClustered", "true");
prop.put("org.quartz.jobStore.clusterCheckinInterval", "15000");
prop.put("org.quartz.jobStore.maxMisfiresToHandleAtATime", "1");

prop.put("org.quartz.jobStore.misfireThreshold", "12000");
prop.put("org.quartz.jobStore.tablePrefix", "QRTZ_");
prop.put("org.quartz.jobStore.selectWithLockSQL", "SELECT * FROM {0}LOCKS UPDLOCK WHERE LOCK_NAME = ?");

//PostgreSQL資料庫,需要開啟此註釋
prop.put("org.quartz.jobStore.driverDelegateClass", "org.quartz.impl.jdbcjobstore.PostgreSQLDelegate");

factory.setQuartzProperties(prop);

factory.setSchedulerName("safeScheduler");
//延時啟動
factory.setStartupDelay(30);
factory.setApplicationContextSchedulerContextKey("applicationContextKey");
//可選,QuartzScheduler 啟動時更新己存在的Job,這樣就不用每次修改targetObject後刪除qrtz_job_details表對應記錄了
factory.setOverwriteExistingJobs(true);
//設定自動啟動,預設為true
factory.setAutoStartup(true);


factory.setJobFactory(springJobFactory);
return factory;
}

@Bean
public Scheduler scheduler(DataSource dataSource) {
return schedulerFactoryBean(dataSource).getScheduler();
}

}
4.4、編寫quartz任務實現類

/**
* @Author haylee
* @Date 2021/4/13 15:50
* @Version 1.0
* @Description TODO
*/
@Service
public class QuartzJobService {

@Autowired
private Scheduler scheduler;

/**
* 建立定時任務Simple
* quartzBean.getInterval()==null表示單次提醒,
* 否則迴圈提醒(quartzBean.getEndTime()!=null)
* @param quartzBean
*/
public void createScheduleJobSimple(QuartzBean quartzBean) throws Exception{
//獲取到定時任務的執行類 必須是類的絕對路徑名稱
//定時任務類需要是job類的具體實現 QuartzJobBean是job的抽象類。
Class<? extends Job> jobClass = (Class<? extends Job>) Class.forName(quartzBean.getJobClass());
// 構建定時任務資訊
JobDetail jobDetail = JobBuilder.newJob(jobClass)
.withIdentity(quartzBean.getJobName(), ObjectUtils.isNotEmpty(quartzBean.getJobGroup()) ?quartzBean.getJobGroup():null)
.setJobData(quartzBean.getJobDataMap())
.build();
// 設定定時任務執行方式
SimpleScheduleBuilder simpleScheduleBuilder = null;
if (quartzBean.getInterval() == null) { //單次
simpleScheduleBuilder = SimpleScheduleBuilder.simpleSchedule();
} else { //迴圈
simpleScheduleBuilder = SimpleScheduleBuilder.repeatMinutelyForever(quartzBean.getInterval());
}
// 構建觸發器trigger
Trigger trigger = null;
if (quartzBean.getInterval() == null) { //單次
trigger = TriggerBuilder.newTrigger()
.withIdentity(quartzBean.getJobName(),ObjectUtils.isNotEmpty(quartzBean.getJobGroup()) ?quartzBean.getJobGroup():null)
.withSchedule(simpleScheduleBuilder)
.startAt(quartzBean.getStartTime())
.build();
} else { //迴圈
trigger = TriggerBuilder.newTrigger()
.withIdentity(quartzBean.getJobName(),ObjectUtils.isNotEmpty(quartzBean.getJobGroup()) ?quartzBean.getJobGroup():null)
.withSchedule(simpleScheduleBuilder)
.startAt(quartzBean.getStartTime())
.endAt(quartzBean.getEndTime())
.build();
}
scheduler.scheduleJob(jobDetail, trigger);
}

/**
* 建立定時任務Cron
* 定時任務建立之後預設啟動狀態
* @param quartzBean 定時任務資訊類
* @throws Exception
*/
public void createScheduleJobCron(QuartzBean quartzBean) throws Exception{
//獲取到定時任務的執行類 必須是類的絕對路徑名稱
//定時任務類需要是job類的具體實現 QuartzJobBean是job的抽象類。
Class<? extends Job> jobClass = (Class<? extends Job>) Class.forName(quartzBean.getJobClass());
// 構建定時任務資訊
JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(quartzBean.getJobName(),ObjectUtils.isNotEmpty(quartzBean.getJobGroup()) ?quartzBean.getJobGroup():null).setJobData(quartzBean.getJobDataMap()).build();
// 設定定時任務執行方式
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(quartzBean.getCronExpression());
// 構建觸發器trigger
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(quartzBean.getJobName()).withSchedule(scheduleBuilder).build();
scheduler.scheduleJob(jobDetail, trigger);
}

/**
* 根據任務名稱暫停定時任務
* @param jobName 定時任務名稱
* @param jobGroup 任務組(沒有分組傳值null)
* @throws Exception
*/
public void pauseScheduleJob(String jobName,String jobGroup) throws Exception{
JobKey jobKey = JobKey.jobKey(jobName,ObjectUtils.isNotEmpty(jobGroup) ?jobGroup:null);
scheduler.pauseJob(jobKey);
}

/**
* 根據任務名稱恢復定時任務
* @param jobName 定時任務名
* @param jobGroup 任務組(沒有分組傳值null)
* @throws SchedulerException
*/
public void resumeScheduleJob(String jobName,String jobGroup) throws Exception {
JobKey jobKey = JobKey.jobKey(jobName,ObjectUtils.isNotEmpty(jobGroup) ?jobGroup:null);
scheduler.resumeJob(jobKey);
}

/**
* 根據任務名稱立即執行一次定時任務
* @param jobName 定時任務名稱
* @param jobGroup 任務組(沒有分組傳值null)
* @throws SchedulerException
*/
public void runOnce(String jobName,String jobGroup) throws Exception{
JobKey jobKey = JobKey.jobKey(jobName,ObjectUtils.isNotEmpty(jobGroup) ?jobGroup:null);
scheduler.triggerJob(jobKey);
}

/**
* 更新定時任務Simple
* @param quartzBean 定時任務資訊類
* @throws SchedulerException
*/
public void updateScheduleJobSimple(QuartzBean quartzBean) throws Exception {
//獲取到對應任務的觸發器
TriggerKey triggerKey = TriggerKey.triggerKey(quartzBean.getJobName(), ObjectUtils.isNotEmpty(quartzBean.getJobGroup()) ?quartzBean.getJobGroup():null);
// 設定定時任務執行方式
SimpleScheduleBuilder simpleScheduleBuilder = null;
if (quartzBean.getInterval() == null) { //單次
simpleScheduleBuilder = SimpleScheduleBuilder.simpleSchedule();
} else { //迴圈
simpleScheduleBuilder = SimpleScheduleBuilder.repeatMinutelyForever(quartzBean.getInterval());
}
// 構建觸發器trigger
Trigger trigger = null;
if (quartzBean.getInterval() == null) { //單次
trigger = TriggerBuilder.newTrigger()
.withIdentity(quartzBean.getJobName(), ObjectUtils.isNotEmpty(quartzBean.getJobGroup()) ?quartzBean.getJobGroup():null)
.withSchedule(simpleScheduleBuilder)
.startAt(quartzBean.getStartTime())
.build();
} else { //迴圈
TriggerBuilder.newTrigger()
.withIdentity(quartzBean.getJobName(), ObjectUtils.isNotEmpty(quartzBean.getJobGroup()) ?quartzBean.getJobGroup():null)
.withSchedule(simpleScheduleBuilder)
.startAt(quartzBean.getStartTime())
.endAt(quartzBean.getEndTime())
.build();
}
//重置對應的job
scheduler.rescheduleJob(triggerKey, trigger);
}

/**
* 更新定時任務Cron
* @param quartzBean 定時任務資訊類
* @throws SchedulerException
*/
public void updateScheduleJobCron(QuartzBean quartzBean) throws Exception {
//獲取到對應任務的觸發器
TriggerKey triggerKey = TriggerKey.triggerKey(quartzBean.getJobName());
//設定定時任務執行方式
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(quartzBean.getCronExpression());
//重新構建任務的觸發器trigger
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
//重置對應的job
scheduler.rescheduleJob(triggerKey, trigger);
}

/**
* 根據定時任務名稱從排程器當中刪除定時任務
* @param jobName 定時任務名稱
* @param jobGroup 任務組(沒有分組傳值null)
* @throws SchedulerException
*/
public void deleteScheduleJob(String jobName,String jobGroup) throws Exception {
JobKey jobKey = JobKey.jobKey(jobName,ObjectUtils.isNotEmpty(jobGroup) ?jobGroup:null);
scheduler.deleteJob(jobKey);
}

/**
* 獲取任務狀態
* @param jobName
* @param jobGroup 任務組(沒有分組傳值null)
* @return
* (" BLOCKED ", " 阻塞 ");
* ("COMPLETE", "完成");
* ("ERROR", "出錯");
* ("NONE", "不存在");
* ("NORMAL", "正常");
* ("PAUSED", "暫停");
*/
public String getScheduleJobStatus(String jobName,String jobGroup) throws Exception {
TriggerKey triggerKey = TriggerKey.triggerKey(jobName,ObjectUtils.isNotEmpty(jobGroup) ?jobGroup:null);
Trigger.TriggerState state = scheduler.getTriggerState(triggerKey);
return state.name();
}

/**
* 根據定時任務名稱來判斷任務是否存在
* @param jobName 定時任務名稱
* @param jobGroup 任務組(沒有分組傳值null)
* @throws SchedulerException
*/
public Boolean checkExistsScheduleJob(String jobName,String jobGroup) throws Exception {
JobKey jobKey = JobKey.jobKey(jobName, ObjectUtils.isNotEmpty(jobGroup) ?jobGroup:null);
return scheduler.checkExists(jobKey);
}

/**
* 根據任務組刪除定時任務
* @param jobGroup 任務組
* @throws SchedulerException
*/
public Boolean deleteGroupJob(String jobGroup) throws Exception {
GroupMatcher<JobKey> matcher = GroupMatcher.groupEquals(jobGroup);
Set<JobKey> jobkeySet = scheduler.getJobKeys(matcher);
List<JobKey> jobkeyList = new ArrayList<JobKey>();
jobkeyList.addAll(jobkeySet);
return scheduler.deleteJobs(jobkeyList);
}

/**
* 根據任務組批量刪除定時任務
* @param jobkeyList
* @throws SchedulerException
*/
public Boolean batchDeleteGroupJob(List<JobKey> jobkeyList) throws Exception {
return scheduler.deleteJobs(jobkeyList);
}

/**
* 根據任務組批量查詢出jobkey
* @param jobGroup 任務組
* @throws SchedulerException
*/
public void batchQueryGroupJob(List<JobKey> jobkeyList,String jobGroup) throws Exception {
GroupMatcher<JobKey> matcher = GroupMatcher.groupEquals(jobGroup);
Set<JobKey> jobkeySet = scheduler.getJobKeys(matcher);
jobkeyList.addAll(jobkeySet);
}

/**
* 根據jobkey查詢job詳情
* @param jobKey
* @return
*/
public JobDetail queryJobDetail(JobKey jobKey){
JobDetail jobDetail = null;
try {
jobDetail = scheduler.getJobDetail(jobKey);
} catch (SchedulerException e) {
e.printStackTrace();
}
return jobDetail;
}
}
4.5、編寫quartz任務實體類

/**
* @Author haylee
* @Date 2021/4/13 15:50
* @Version 1.0
* @Description 任務實體
*/
@Data
public class QuartzBean {

/** 任務id */
private String id;

/** 任務名稱 */
private String jobName;

/** 任務組 */
private String jobGroup;

/** 任務執行類 */
private String jobClass;

/** 任務狀態 啟動還是暫停*/
private Integer status;

/**
* 任務開始時間
*/
private Date startTime;

/**
* 任務迴圈間隔-單位:分鐘
*/
private Integer interval;

/**
* 任務結束時間
*/
private Date endTime;

/** 任務執行時間表達式 */
private String cronExpression;

private JobDataMap jobDataMap;
}
4.6、編寫quartz任務類

Spring Boot定時任務spring-boot-starter-quartz通過@DisallowConcurrentExecution禁止任務併發執行

@DisallowConcurrentExecution //
@Component
public class MyTask extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
JobKey jobKey = context.getJobDetail().getKey();
JobDataMap map = context.getJobDetail().getJobDataMap();
String userId = map.getString("userId");
System.out.println("SimpleJob says: " + jobKey + ", userId: " + userId + " executing at " + new Date());
}
}
4.7、編寫quartz任務操作

@RestController
@RequestMapping("/api/quartz/")
public class JobController {

@Autowired
private QuartzJobService quartzJobService;

//建立&啟動
@GetMapping("startSimpleJob")
public String startSimpleJob() throws SchedulerException, ClassNotFoundException, ParseException {
QuartzBean quartzBean = new QuartzBean();
quartzBean.setJobClass("com.quartz.demo.job.MyTask");
quartzBean.setJobName("job1");
JobDataMap map = new JobDataMap();
map.put("userId", "123456");
quartzBean.setJobDataMap(map);
Date now = new Date();
quartzBean.setStartTime(DateUtils.addSeconds(now, 10));
quartzBean.setInterval(10);
quartzBean.setEndTime(DateUtils.addMinutes(now, 1));
try {
quartzJobService.createScheduleJobSimple(quartzBean);
} catch (Exception e) {
e.printStackTrace();
}
return "startJob Success!";
}

/**
* 建立cron Job
* @param quartzBean
* @return
*/
@RequestMapping("/createCronJob")
@ResponseBody
public String createJob(QuartzBean quartzBean) {
try {
//進行測試所以寫死
quartzBean.setJobClass("com.quartz.demo.job.MyTask");
quartzBean.setJobName("job1");
quartzBean.setCronExpression("*/5 * * * * ?");
quartzJobService.createScheduleJobCron(quartzBean);
} catch (Exception e) {
return "建立失敗";
}
return "建立成功";
}

/**
* 暫停job
* @return
*/
@RequestMapping(value = {"/pauseJob/{jobName}","/pauseJob/{jobName}/{jobGroup}"})
@ResponseBody
public String pauseJob(@PathVariable("jobName") String jobName,@PathVariable(required = false) String jobGroup) {
try {
quartzJobService.pauseScheduleJob(jobName,ObjectUtils.isNotEmpty(jobGroup) ?jobGroup:null);
} catch (Exception e) {
return "暫停失敗";
}
return "暫停成功";
}

@RequestMapping(value = {"/resume/{jobName}","/resume/{jobName}/{jobGroup}"})
@ResponseBody
public String resume(@PathVariable("jobName") String jobName,@PathVariable(required = false) String jobGroup) {
try {
quartzJobService.resumeScheduleJob(jobName,ObjectUtils.isNotEmpty(jobGroup) ?jobGroup:null);
} catch (Exception e) {
return "啟動失敗";
}
return "啟動成功";
}

@RequestMapping(value = {"/delete/{jobName}","/delete/{jobName}/{jobGroup}"})
public String delete(@PathVariable("jobName") String jobName,@PathVariable(required = false) String jobGroup) {
try {
quartzJobService.deleteScheduleJob(jobName,ObjectUtils.isNotEmpty(jobGroup) ?jobGroup:null);
} catch (Exception e) {
return "刪除失敗";
}
return "刪除成功";
}

@RequestMapping(value = {"/check/{jobName}","/check/{jobName}/{jobGroup}"})
public String check(@PathVariable("jobName") String jobName,@PathVariable(required = false) String jobGroup) {
try {
if(quartzJobService.checkExistsScheduleJob(jobName,ObjectUtils.isNotEmpty(jobGroup) ?jobGroup:null)){
return "存在定時任務:"+jobName;
}else{
return "不存在定時任務:"+jobName;
}
} catch (Exception e) {
return "查詢任務失敗";
}
}

@RequestMapping(value = {"/status/{jobName}","/status/{jobName}/{jobGroup}"})
@ResponseBody
public String status(@PathVariable("jobName") String jobName,@PathVariable(required = false) String jobGroup) {
try {
return quartzJobService.getScheduleJobStatus(jobName,ObjectUtils.isNotEmpty(jobGroup) ?jobGroup:null);
} catch (Exception e) {
return "獲取狀態失敗";
}
//return "獲取狀態成功";
}

}
五、springboot叢集部署測試

springboot服務叢集部署,各個節點的系統時間必須一致,資料必須保證連線同一個資料庫,這樣才能做到分散式任務排程,任務不重複執行。

六、XXL-JOB(在quartz基礎上封裝的一個開源排程平臺,有興趣的可以研究下)

XXL-JOB是一個分散式任務排程平臺,其核心設計目標是開發迅速、學習簡單、輕量級、易擴充套件。現已開放原始碼並接入多家公司線上產品線,開箱即用。


————————————————
版權宣告:本文為CSDN博主「hay_lee」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處連結及本宣告。
原文連結:https://blog.csdn.net/yztezhl/article/details/124142960