开篇说明

  • 如果在这里获得过启发和思考,希望点赞支持!对于内容有不同的看法欢迎来信交流。
  • 技术栈 >> java
  • 邮箱 >> 15673219519@163.com

描述

  • 之前项目刚刚开始简易的通过实现单机版。 quartz定时任务,两张表实现数据持久化
  • 由于项目持续了大半年的迭代更新,需要执行的定时任务增多。并且服务中还有其他业务,单机版本的定时任务影响了服务的集群搭建。所以,着手对其改进。
  • 目标是保留单机版中两张表的使用,来对定时任务执行计划在页面控制,以及可查看每个任务的执行结果等信息。

我的思路

  • 通过quartz官方提供的11张数据表作为数据存储,来实现集群服务的数据共享。
  • 自定义表schedule_job 作为任务的初始化,以及后续对执行计划的修改,自定义表schedule_log作为执行记录的存储。此处与单机版一致。
  • 定时任务监听器ScheduleJobListener.java记录任务的执行结果。
  • 注意:与单机版不同的是,集群版的数据存储不在内存中而是在官方提供的11各表中。由于将启动多个服务,故,在初始化任务时需要判断该定时任务是否已经存在。(单机时内存中的数据每次停机均会清空,而集群中数据库中的数据却会一值保留,除非重新创建表)。因此我们需要修改ScheduleJobUtil工具类,以及初始化时候的逻辑。
  • 此外我们还需要对添加一些数据源的配置。

第一步:添加依赖

<!--quartz依赖-->
<dependency><groupId>org.quartz-scheduler</groupId><artifactId>quartz</artifactId><version>2.2.3</version>
</dependency>
<dependency><groupId>org.springframework</groupId><artifactId>spring-context-support</artifactId>
</dependency>
<dependency><groupId>org.quartz-scheduler</groupId><artifactId>quartz-jobs</artifactId><version>2.2.3</version>
</dependency>

第二步:创建数据表

  • 自定义的两个表,参考开篇中的单机版博客
  • 11个表均由官方提供
-- 定时任务所需的数据表
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(200) NOT NULL,
JOB_GROUP VARCHAR(200) 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(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(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(200) 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(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) 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(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))
ENGINE=InnoDB;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 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(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) 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(200) 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(200) 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(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
INSTANCE_NAME VARCHAR(200) 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(200) NULL,
JOB_GROUP VARCHAR(200) 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(200) 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;

第三步:java代码实现

  • 添加配置 quartz.properties
#quartz集群配置
#调度标识名 集群中每一个实例都必须使用相同的名称
org.quartz.scheduler.instanceName=DefaultQuartzScheduler
#ID设置为自动获取 每一个必须不同
org.quartz.scheduler.instanceId=AUTO
org.quartz.scheduler.makeSchedulerThreadDaemon=true
#线程池的实现类(一般使用SimpleThreadPool即可满足需求)
org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
#指定在线程池里面创建的线程是否是守护线程
org.quartz.threadPool.makeThreadsDaemons=true
#指定线程数,至少为1(无默认值)
org.quartz.threadPool.threadCount:20
#设置线程的优先级(最大为java.lang.Thread.MAX_PRIORITY 10,最小为Thread.MIN_PRIORITY 1,默认为5)
org.quartz.threadPool.threadPriority:5
#数据保存方式为数据库持久化
org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
#数据库代理类,一般org.quartz.impl.jdbcjobstore.StdJDBCDelegate可以满足大部分数据库
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#表的前缀,默认QRTZ_
org.quartz.jobStore.tablePrefix=QRTZ_
#是否加入集群
org.quartz.jobStore.isClustered=true
# 信息保存时间 默认值60秒
org.quartz.jobStore.misfireThreshold=25000
  • ScheduleJobListener.java 定时任务监听器:通过监听的方式记录 记录定时任务的执行记录。
import com.qykj.admin.service.schedule.JobService;
import com.qykj.core.constants.schedule.JobLogStatusEnum;
import com.qykj.core.util.DateUtil;
import com.qykj.core.util.SpringUtils;
import org.apache.logging.log4j.ThreadContext;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobListener;
import org.springframework.stereotype.Component;
import java.util.UUID;/*** =====================================================================================================================* jiangshaoneng <15673219519.@163.com> 2021/11/20 14:18** 定时任务监听器:通过监听的方式记录 记录定时任务的执行记录。* =====================================================================================================================*/
@Component
public class ScheduleJobListener implements JobListener{private JobService jobService;// 用于保存 logIdprivate ThreadLocal<String> threadLocalLogId = new ThreadLocal<>();// startTimeprivate ThreadLocal<Long> threadLocalStartTime = new ThreadLocal<>();@Overridepublic String getName() {return "myJobListener";}@Overridepublic void jobToBeExecuted(JobExecutionContext jobExecutionContext) {ThreadContext.put("traceID", UUID.randomUUID().toString().replace("-",""));if(jobService == null){jobService = SpringUtils.getBean(JobService.class);}JobDetail jobDetail = jobExecutionContext.getJobDetail();String name = jobDetail.getKey().getName();String logId = jobService.saveScheduleLog(name, JobLogStatusEnum.RUNNING.getCode(), "");threadLocalLogId.set(logId); // 把执行记录放到 threadLocal,提供给执行结束后取此结果threadLocalStartTime.set(DateUtil.getCurrentTime());}@Overridepublic void jobExecutionVetoed(JobExecutionContext jobExecutionContext) {String logId = threadLocalLogId.get(); // 执行记录logIdlong runTime = DateUtil.getCurrentTime() - threadLocalStartTime.get();jobService.updateScheduleLog(logId, JobLogStatusEnum.ERROR.getCode(), "执行失败",runTime);}@Overridepublic void jobWasExecuted(JobExecutionContext jobExecutionContext, JobExecutionException e) {String logId = threadLocalLogId.get(); // 执行记录logIdlong runTime = DateUtil.getCurrentTime() - threadLocalStartTime.get();if(e == null){ // 没有异常修改记录为成功jobService.updateScheduleLog(logId, JobLogStatusEnum.SUCCESS.getCode(), "执行成功",runTime);}else{ // 存在异常修改记录失败,并且把异常信息保存到数据库中jobService.updateScheduleLog(logId, JobLogStatusEnum.ERROR.getCode(), e.toString(), runTime);}}
}
  • ScheduleConfig.java 定时任务配置类,配置数据源,监听器等信息
import com.qykj.admin.job.ScheduleJobListener;
import com.qykj.admin.job.ScheduleJobUtil;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.PropertiesFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import javax.sql.DataSource;
import java.io.IOException;
import java.util.Properties;
import java.util.concurrent.Executor;
import static org.quartz.impl.matchers.GroupMatcher.jobGroupEquals;/*** =====================================================================================================================* jiangshaoneng <15673219519.@163.com> 22021/11/20 14:18** 定时任务配置类。* =====================================================================================================================*/
@Configuration
public class SchedulerConfig {@Qualifier("writeDataSource")@Autowiredprivate DataSource dataSource;@Beanpublic Scheduler scheduler() throws IOException{Scheduler scheduler = schedulerFactoryBean().getScheduler();try {// 默认将 MyJobListener 绑定到 JOB_GROUP_NAME,在MyJobListener通过监听的方式记录 记录定时任务的执行记录scheduler.getListenerManager().addJobListener(new ScheduleJobListener(), jobGroupEquals(ScheduleJobUtil.JOB_GROUP_NAME));} catch (SchedulerException e) {e.printStackTrace();}return scheduler;}@Beanpublic SchedulerFactoryBean schedulerFactoryBean() throws IOException{SchedulerFactoryBean factory = new SchedulerFactoryBean();factory.setSchedulerName("cluster_scheduler");factory.setDataSource(dataSource);factory.setApplicationContextSchedulerContextKey("application");factory.setQuartzProperties(quartzProperties());factory.setTaskExecutor(schedulerThreadPool());factory.setStartupDelay(0);return factory;}@Beanpublic Properties quartzProperties() throws IOException {PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties"));propertiesFactoryBean.afterPropertiesSet();return propertiesFactoryBean.getObject();}@Beanpublic Executor schedulerThreadPool(){ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();executor.setCorePoolSize(Runtime.getRuntime().availableProcessors());executor.setMaxPoolSize(Runtime.getRuntime().availableProcessors());executor.setQueueCapacity(Runtime.getRuntime().availableProcessors());return executor;}
}
  • ScheduleJobUtils.java 定时任务工具类,包括任务的增删改查,已配置数据库的情况,因此所有操作都是针对数据库中的操作。(jar包已经集成,只需调用对应方法即可,无需我们实现数据库操作)
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;/*** =====================================================================================================================* jiangshaoneng <15673219519.@163.com> 2021/11/20 14:18** 任务调度的工具类,包括定时任务的新增修改删除等。本系统暂时对任务组,触发器组名暂时均使用同一个。* 如:JOB_GROUP_NAME,TRIGGER_GROUP_NAME* =====================================================================================================================*/
@Slf4j
@Component
public class ScheduleJobUtil {@Autowiredprivate Scheduler scheduler;public static String JOB_GROUP_NAME = "DEFAULT_JOB_GROUP";private static String TRIGGER_GROUP_NAME = "DEFAULT_TRIGGER_GROUP";public ScheduleJobUtil(){}/*** @Description: 添加一个定时任务,使用默认的任务组名,触发器名,触发器组名* @param jobName 任务名* @param cls 任务* @param cron 时间设置,参考quartz说明文档* @throws SchedulerException*/public void addJob(String jobName, Class cls, String cron, JobDataMap dataMap) throws SchedulerException {TriggerKey triggerKey = TriggerKey.triggerKey(jobName, TRIGGER_GROUP_NAME);Trigger trigger = scheduler.getTrigger(triggerKey);if(trigger != null){log.info("添加任务:{},{},{} 已存在", jobName, cls, cron);return;}// 用于描叙Job实现类及其他的一些静态信息,构建一个作业实例JobDetail jobDetail = JobBuilder.newJob(cls).withIdentity(jobName, JOB_GROUP_NAME).build();// 构建一个触发器,规定触发的规则trigger = TriggerBuilder.newTrigger()// 创建一个新的TriggerBuilder来规范一个触发器.withIdentity(jobName, TRIGGER_GROUP_NAME)// 给触发器起一个名字和组名.startNow()// 立即执行.withSchedule(CronScheduleBuilder.cronSchedule(cron)) // 触发器的执行时间.usingJobData(dataMap) // 定时器一些简单的数据.build();// 产生触发器scheduler.scheduleJob(jobDetail, trigger);log.info("添加任务:{},{},{}", jobName, cls, cron);// 启动if (!scheduler.isShutdown()) {scheduler.start();}}/*** @Description: 添加一个定时任务** @param jobName 任务名* @param jobGroupName 任务组名* @param triggerName 触发器名* @param triggerGroupName 触发器组名* @param cls 任务* @param cron 时间设置,参考quartz说明文档*/public void addJob(String jobName, String jobGroupName, String triggerName, String triggerGroupName, Class cls, String cron) throws SchedulerException {TriggerKey triggerKey = TriggerKey.triggerKey(jobName, TRIGGER_GROUP_NAME);Trigger trigger = scheduler.getTrigger(triggerKey);if(trigger != null){log.info("添加任务:{},{},{},{},{},{} 已存在",jobName,jobGroupName,triggerName,triggerGroupName,cls,cron);return;}// 用于描叙Job实现类及其他的一些静态信息,构建一个作业实例JobDetail jobDetail = JobBuilder.newJob(cls).withIdentity(jobName, jobGroupName).build();// 构建一个触发器,规定触发的规则trigger = TriggerBuilder.newTrigger()// 创建一个新的TriggerBuilder来规范一个触发器.withIdentity(jobName, triggerGroupName)// 给触发器起一个名字和组名.startNow()// 立即执行.withSchedule(CronScheduleBuilder.cronSchedule(cron)) // 触发器的执行时间.build();// 产生触发器scheduler.scheduleJob(jobDetail, trigger);log.info("添加任务:{},{},{},{},{},{}",jobName,jobGroupName,triggerName,triggerGroupName,cls,cron);// 启动if (!scheduler.isShutdown()) {scheduler.start();}}/*** @Description: 修改一个任务的触发时间(使用默认的任务组名,触发器名,触发器组名)** @param jobName* @param cron* @throws SchedulerException*/public void modifyJobTime(String jobName, String cron, JobDataMap dataMap) throws SchedulerException {TriggerKey triggerKey = new TriggerKey(jobName, TRIGGER_GROUP_NAME);CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);if (trigger == null) {log.info("修改任务失败,此任务不存在:{},{}", jobName, cron);return;}String oldTime = trigger.getCronExpression();if (!oldTime.equalsIgnoreCase(cron)) {JobDetail jobDetail = scheduler.getJobDetail(new JobKey(jobName, JOB_GROUP_NAME));Class objJobClass = jobDetail.getJobClass();removeJob(jobName);addJob(jobName, objJobClass, cron, dataMap);log.info("修改任务:{},{}",jobName,cron);}}/*** @Description: 修改或添加一个任务,jobName 存在时修改任务,不存在时则新增任务**/public void modifyOrAddJobTime(String jobName, Class cls, String cron, JobDataMap dataMap) throws SchedulerException {TriggerKey triggerKey = new TriggerKey(jobName, TRIGGER_GROUP_NAME);CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);if (trigger == null) {addJob(jobName, cls, cron, dataMap);log.info("添加任务:{},{}",jobName,cron);}else{String oldTime = trigger.getCronExpression();JobDataMap oldJobDataMap = trigger.getJobDataMap();if (!oldTime.equalsIgnoreCase(cron) || !oldJobDataMap.equals(dataMap)) {JobDetail jobDetail = scheduler.getJobDetail(new JobKey(jobName, JOB_GROUP_NAME));Class objJobClass = jobDetail.getJobClass();removeJob(jobName);addJob(jobName, objJobClass, cron, dataMap);log.info("修改任务:{},{}",jobName,cron);}else {log.info("任务存在:{},{}",jobName,cron);}}}/*** @Description: 移除一个任务(使用默认的任务组名,触发器名,触发器组名)* @param jobName* @throws SchedulerException*/public void removeJob(String jobName) throws SchedulerException {JobKey jobKey = new JobKey(jobName, TRIGGER_GROUP_NAME);// 停止触发器scheduler.pauseJob(jobKey);scheduler.unscheduleJob(new TriggerKey(jobName, TRIGGER_GROUP_NAME));// 移除触发器scheduler.deleteJob(jobKey);// 删除任务log.info("移除任务:{}",jobName);}/*** 移除任务** @param jobName* @param jobGroupName* @param triggerName* @param triggerGroupName* @throws SchedulerException*/public void removeJob(String jobName, String jobGroupName, String triggerName, String triggerGroupName) throws SchedulerException {JobKey jobKey = new JobKey(jobName, jobGroupName);// 停止触发器scheduler.pauseJob(jobKey);scheduler.unscheduleJob(new TriggerKey(jobName, triggerGroupName));// 移除触发器scheduler.deleteJob(jobKey);// 删除任务log.info("移除任务:{},{},{},{}",jobName,jobGroupName,triggerName,triggerGroupName);}/*** 启动所有任务* @throws SchedulerException*/public void startJobs() throws SchedulerException {scheduler.start();log.info("启动所有任务");}/*** 关闭所有定时任务* @throws SchedulerException*/public void shutdownJobs() throws SchedulerException {if (!scheduler.isShutdown()) {scheduler.shutdown();log.info("关闭所有任务");}}
}
  • ScheduleJobInitListener.java 监听项目启动时初始化定时任务
import com.qykj.admin.service.schedule.JobService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;/*** =====================================================================================================================* jiangshaoneng <15673219519.@163.com> 2021/11/20 14:18** 启动项目时,启动数据库配置启动的定时任务。可支持集权部署* =====================================================================================================================*/
@Slf4j
@Component
public class ScheduleJobInitListener implements ApplicationListener<ContextRefreshedEvent>{@Autowiredprivate JobService jobService;@Overridepublic void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {log.info("开始初始化定时任务 ...");jobService.initScheduleJob();log.info("初始化定时任务成功 ...");}
}
  • ScheduleService.java 定时任务初始化,修改等逻辑
import com.qykj.admin.job.ScheduleJobUtil;
import com.qykj.core.domain.entity.schedule.ScheduleJob;
import com.qykj.core.domain.entity.schedule.ScheduleLog;
import com.qykj.core.exception.AppException;
import com.qykj.core.exception.ErrorCode;
import com.qykj.core.util.JsonUtils;
import com.qykj.repo.impl.schedule.ScheduleJobRepoImpl;
import com.qykj.repo.impl.schedule.ScheduleLogRepoImpl;
import org.quartz.JobDataMap;
import org.quartz.SchedulerException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;@Service
public class JobService {private static final Logger logger = LoggerFactory.getLogger(JobService.class);@Autowiredprivate ScheduleJobRepoImpl scheduleJobRepo;@Autowiredprivate ScheduleLogRepoImpl scheduleLogRepo;@Autowiredprivate ScheduleJobUtil scheduleJobUtil;/*** 查看所有任务列表*/public List<ScheduleJob> getAllJob(){return scheduleJobRepo.getList();}/*** 查看某个任务的执行详情*/public List<ScheduleLog> getJobScheduleDetail(int jobId){return scheduleLogRepo.getListByJobId(jobId);}/*** 查看某个任务的详情*/public ScheduleJob getJobDetail(int jobId){return scheduleJobRepo.getScheduleJobById(jobId);}/*** 修改任务*/public Map<String,Object> updateJob(ScheduleJob scheduleJob){Map<String,Object> result = new HashMap<>();try {Class<?> clszz = Class.forName(scheduleJob.getClazz());Integer status = scheduleJob.getStatus();if(status == 1){ // 修改或者添加任务Map<String, String> map = (Map<String, String>) JsonUtils.json2Map(scheduleJob.getJobDataJson());JobDataMap dataMap = new JobDataMap(map); // 配置定时器需要的数据scheduleJobUtil.modifyOrAddJobTime(scheduleJob.getName(), clszz, scheduleJob.getCron(), dataMap);}else if(status == 2){ // 停止任务scheduleJobUtil.removeJob(scheduleJob.getName());}else{return result;}}catch (SchedulerException e){logger.error("修改定时任务异常:{}", e.toString());throw new AppException(ErrorCode.SYS_ERROR);}catch (ClassNotFoundException e){logger.error("修改定时任务异常,无法找到指定的任务类");throw new AppException(ErrorCode.SYS_PARAMS_ERROR.code(), "无法找到指定的任务类");}scheduleJobRepo.updateScheduleJob(scheduleJob);return result;}/*** 启动数据库中配置的定时任务*/public void initScheduleJob(){// 移除不需要执行的定时任务List<ScheduleJob> disableList = scheduleJobRepo.getDisableList();for (ScheduleJob scheduleJob: disableList){try {scheduleJobUtil.removeJob(scheduleJob.getName());} catch (SchedulerException e) {logger.error("移除定时任务:{}异常:{}", scheduleJob.getName(), e.toString());}}// 添加需要执行的定时任务List<ScheduleJob> enableList = scheduleJobRepo.getEnableList();for (ScheduleJob scheduleJob: enableList){Map<String, String> map = new HashMap<>();try {map = (Map<String, String>) JsonUtils.json2Map(scheduleJob.getJobDataJson());}catch (Exception e){logger.error("定时任务:{},DataJson格式不合法", scheduleJob.getName());}try {JobDataMap dataMap = new JobDataMap(map); // 配置定时器需要的数据Class<?> clszz = Class.forName(scheduleJob.getClazz());scheduleJobUtil.modifyOrAddJobTime(scheduleJob.getName(), clszz, scheduleJob.getCron(), dataMap);} catch (SchedulerException e){logger.error("初始化启动定时任务:{},异常:{}", scheduleJob.getName(), e.toString());continue;} catch (ClassNotFoundException e){logger.error("初始化启动定时任务异常,无法找到指定的任务类");continue;}}}/*** 新增一条执行日志*/public String saveScheduleLog(String name, int status, String logInfo){ScheduleJob scheduleJob = scheduleJobRepo.getScheduleJobByName(name);ScheduleLog scheduleLog = new ScheduleLog(scheduleJob,status,logInfo);scheduleLogRepo.saveScheduleLog(scheduleLog);return scheduleLog.getId();}/*** 更新一条执行日志*/public void updateScheduleLog(String id, int status, String logInfo, long runTime){scheduleLogRepo.updateScheduleLog(id,status,logInfo,runTime);}
}
  • ScheduleController.java 提供页面操作的接口
import com.qykj.admin.aspect.AuthPermissions;
import com.qykj.admin.service.schedule.JobService;
import com.qykj.core.domain.entity.schedule.ScheduleJob;
import com.qykj.core.domain.entity.schedule.ScheduleLog;
import com.qykj.core.view.MSG;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;@RestController
@RequestMapping("/v2/schedule/job")
@Api(tags = "Schedule:001-定时任务控制管理")
public class JobControllerV2 {@Autowiredprivate JobService jobService;@ApiOperation(value = "查看所有任务列表")@GetMapping("/list")public MSG<List<ScheduleJob>> getJobList(){List<ScheduleJob> allJob = jobService.getAllJob();return MSG.SUCCESS(allJob);}@ApiOperation(value = "查看某个任务的执行详情")@GetMapping("/schedule/detail")public MSG<List<ScheduleLog>> getJobScheduleDetail(@RequestParam("jobId") int jobId){List<ScheduleLog> jobScheduleDetail = jobService.getJobScheduleDetail(jobId);return MSG.SUCCESS(jobScheduleDetail);}@ApiOperation(value = "查看某个任务的详情")@GetMapping("/detail")public MSG<ScheduleJob> getJobDetail(@RequestParam("jobId") int jobId){ScheduleJob jobDetail = jobService.getJobDetail(jobId);return MSG.SUCCESS(jobDetail);}@ApiOperation(value = "修改任务")@PostMapping("/update")public MSG updateJob(@RequestBody ScheduleJob job){jobService.updateJob(job);return MSG.SUCCESS();}
}

quartz定时任务集群版相关推荐

  1. Spring+Quartz定时任务集群环境下部署的解决方法

    1.配置quartz.properties文件,如下 #======================================================================== ...

  2. quartz分布式集群部署并且可视化配置job定时任务

    2019独角兽企业重金招聘Python工程师标准>>> 项目使用quartz框架完成了定时任务集群部署调度,并且对quartz进一步封装完成在web界面可动态配置定时任务.定时任务如 ...

  3. Spring+Quartz定时任务调度

    Spring+Quartz定时任务调度   2011/5 zqhxuyuan@gmail.com 参考文章: http://www.iteye.com/topic/399980 ­ org.sprin ...

  4. quartz在集群环境下的最终解决方案

    在集群环境下,大家会碰到一直困扰的问题,即多个 APP 下如何用 quartz 协调处理自动化 JOB . 大家想象一下,现在有 A , B , C3 台机器同时作为集群服务器对外统一提供 SERVI ...

  5. SpringCloud(第 054 篇)简单 Quartz-Cluster 微服务,采用注解配置 Quartz 分布式集群...

    2019独角兽企业重金招聘Python工程师标准>>> SpringCloud(第 054 篇)简单 Quartz-Cluster 微服务,采用注解配置 Quartz 分布式集群 一 ...

  6. SpringCloud(第 054 篇)简单 Quartz-Cluster 微服务,采用注解配置 Quartz 分布式集群... 1

    SpringCloud(第 054 篇)简单 Quartz-Cluster 微服务,采用注解配置 Quartz 分布式集群 - 一.大致介绍 1.因网友提到有没有采用注解式配置的Quartz例子,因此 ...

  7. SpringBoot集成quartz定时调度任务并通过JDBC持久化

    SpringBoot集成quartz定时调度任务并通过JDBC持久化 话不多说上干货 项目pom依赖 配置类 抽象出调度任务实体类 调度执行和调度任务更改工具类 调度配置与执行的代码完毕,下面就是对持 ...

  8. 阿里云Redis集群版简要介绍

    阿里云云数据库Redis集群版火热抢购,详情参见>> 产品简介 云数据库 Redis 提供集群版实例,轻松突破 Redis 自身单线程瓶颈,可极大满足对于 Redis 大容量或高性能的业务 ...

  9. 使用Spring Data Redis操作Redis(集群版)

    说明:请注意Spring Data Redis的版本以及Spring的版本!最新版本的Spring Data Redis已经去除Jedis的依赖包,需要自行引入,这个是个坑点.并且会与一些低版本的Sp ...

  10. Quartz定时框架CronTrigger开发使用实例

    Quartz定时框架CronTrigger开发使用 public class HelloJob implements Job {public void execute(JobExecutionCont ...

最新文章

  1. EntityFramework+DomainDataSource+Silverlight完成数据读取分页排序与修改
  2. okhttp通过post发送Json数据到php 更新数据库
  3. ubuntu14.04配置中文latex完美环境(texlive+texmaker+lyx)
  4. [唐诗]182宫中行乐词(其一)-李白
  5. Java数组的基本知识点
  6. 光纤传感器实验模块_准分布式光纤光栅传感器(光纤光栅串)的概念
  7. DirectX学习笔记_关于Sprite.Draw2D的说明
  8. 电商网站攻防,三个制胜锦囊
  9. 【英语学习】【English L06】U08 News L4 A piece of great news
  10. SpringBoot整合Redis代码相关配置
  11. 还在死磕 Ajax?那可就 out 了!
  12. jQuery的几种简单实用效果
  13. CSS布局:三栏布局,中间栏固定宽度,左右两边自适应
  14. 如何让一个函数返回多个值(C#)
  15. 安卓手机android文件,安卓Android手机系统内文件夹目录解释
  16. 【计算机体系结构实验】MIPS指令系统和MIPS体系结构
  17. 小学生必积累的名人名言汇总100条
  18. iOS15适配本地通知功能
  19. 最本质的相机内参intrinsics与外参extrinsics分析,从建模,推导到求解
  20. Office 365入门教程(一):开始使用Office 365

热门文章

  1. 全国火车高铁站及车次数据爬虫(内含100W+数据,免费领取!)
  2. FFmpeg系列-2-命令行工具之FFmpeg
  3. 线性链表的建立与插入-----数据结构与算法笔记
  4. android 点击按钮打开浏览器网页
  5. 2022年信息安全工程师考试知识点:Web安全
  6. 模拟电子技术 PN结的形成与工作原理 个人笔记
  7. dcp7080d怎么加墨粉_兄弟打印机DCP 7080D提示更换墨粉该怎么办-
  8. mysql解压版安装教程
  9. 四、公文流转的基本过程
  10. 如何在matlab坐标轴上输入希腊字符和开根号符号