最为罕用定时工作框架是Quartz,并且Spring也集成了Quartz的框架,Quartz不仅反对单实例形式还反对分布式形式。本文次要介绍Quartz,根底的Quartz的集成案例本,以及实现基于数据库的分布式工作治理和管制job生命周期。@pdai
SpringBoot定时工作 - 根底quartz实现形式
筹备知识点
- 什么是Quartz
- Quartz的体系结构
- 什么是Quartz长久化
- 实现案例 - 单实例形式
实现案例 - 分布式形式
- 后端实现
- 前端实现
- 测试成果
- 示例源码
- 参考文章
筹备知识点
须要理解罕用的Quartz框架。
什么是Quartz
起源百度百科, 官网地址:http://www.quartz-scheduler.org/
Quartz是OpenSymphony开源组织在Job scheduling畛域又一个开源我的项目,它能够与J2EE与J2SE应用程序相结合也能够独自应用。Quartz能够用来创立简略或为运行十个,百个,甚至是好几万个Jobs这样简单的程序。Jobs能够做成规范的Java组件或 EJBs。
它的特点如下
- 纯java实现,能够作为独立的应用程序,也能够嵌入在另一个独立式利用程序运行
- 弱小的调度性能,Spring默认的调度框架,灵便可配置;
- 作业长久化,调度环境长久化机制,能够保留并复原调度现场。零碎敞开数据不会失落;灵便的利用形式,能够任意定义触发器的调度时间表,反对工作和调度各种组合,组件式监听器、各种插件、线程池等性能,多种存储形式等;
- 分布式和集群能力,能够被实例化,一个Quartz集群中的每个节点作为一个独立的Quartz应用,通过雷同的数据库表来感知到另一个Quartz利用
Quartz的体系结构
- Job 示意一个工作,要执行的具体内容。
- JobDetail 示意一个具体的可执行的调度程序,Job 是这个可执行程调度程序所要执行的内容,另外 JobDetail 还蕴含了这个任务调度的计划和策略。
- Trigger 代表一个调度参数的配置,什么时候去调。
- Scheduler 代表一个调度容器,一个调度容器中能够注册多个 JobDetail 和 Trigger。当 Trigger 与 JobDetail 组合,就能够被 Scheduler 容器调度了。
注: 上图来源于https://www.cnblogs.com/jijm1...
什么是Quartz长久化
- 为什么要长久化?
当程序忽然被中断时,如断电,内存超出时,很有可能造成工作的失落。 能够将调度信息存储到数据库外面,进行长久化,当程序被中断后,再次启动,依然会保留中断之前的数据,继续执行,而并不是从新开始。
- Quartz提供了两种长久化形式
Quartz提供两种根本作业存储类型:
- RAMJobStore
在默认状况下Quartz将任务调度的运行信息保留在内存中,这种办法提供了最佳的性能,因为内存中数据拜访最快。不足之处是不足数据的持久性,当程序道路进行或零碎解体时,所有运行的信息都会失落。
- JobStoreTX
所有的工作信息都会保留到数据库中,能够管制事物,还有就是如果应用服务器敞开或者重启,工作信息都不会失落,并且能够复原因服务器敞开或者重启而导致执行失败的工作。
实现案例 - 单实例形式
本例将展现quartz实现单实例形式。
- 引入POM依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-quartz</artifactId></dependency>
- 定义Job
只须要继承QuartzJobBean,并重载executeInternal办法即可定义你本人的Job执行逻辑。
@Slf4jpublic class HelloJob extends QuartzJobBean { @Override protected void executeInternal(JobExecutionContext context) throws JobExecutionException { // get parameters context.getJobDetail().getJobDataMap().forEach( (k, v) -> log.info("param, key:{}, value:{}", k, v) ); // your logics log.info("Hello Job执行工夫: " + new Date()); }}
- 配置Job
JobDetail, Trigger, Schedule(这里采纳CronScheduleBuilder)
/** * @author pdai */@Configurationpublic class QuartzConfig { @Bean("helloJob") public JobDetail helloJobDetail() { return JobBuilder.newJob(HelloJob.class) .withIdentity("DateTimeJob") .usingJobData("msg", "Hello Quartz") .storeDurably()//即便没有Trigger关联时,也不须要删除该JobDetail .build(); } @Bean public Trigger printTimeJobTrigger() { // 每秒执行一次 CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule("0/1 * * * * ?"); return TriggerBuilder.newTrigger() .forJob(helloJobDetail()) .withIdentity("quartzTaskService") .withSchedule(cronScheduleBuilder) .build(); }}
- 执行测试
2021-10-01 13:09:00.380 INFO 38484 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)2021-10-01 13:09:00.391 INFO 38484 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]2021-10-01 13:09:00.392 INFO 38484 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.50]2021-10-01 13:09:00.526 INFO 38484 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext2021-10-01 13:09:00.526 INFO 38484 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1424 ms2021-10-01 13:09:00.866 INFO 38484 --- [ main] org.quartz.impl.StdSchedulerFactory : Using default implementation for ThreadExecutor2021-10-01 13:09:00.877 INFO 38484 --- [ main] org.quartz.core.SchedulerSignalerImpl : Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl2021-10-01 13:09:00.877 INFO 38484 --- [ main] org.quartz.core.QuartzScheduler : Quartz Scheduler v.2.3.2 created.2021-10-01 13:09:00.878 INFO 38484 --- [ main] org.quartz.simpl.RAMJobStore : RAMJobStore initialized.2021-10-01 13:09:00.878 INFO 38484 --- [ main] org.quartz.core.QuartzScheduler : Scheduler meta-data: Quartz Scheduler (v2.3.2) '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 10 threads. Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered.2021-10-01 13:09:00.878 INFO 38484 --- [ main] org.quartz.impl.StdSchedulerFactory : Quartz scheduler 'quartzScheduler' initialized from an externally provided properties instance.2021-10-01 13:09:00.879 INFO 38484 --- [ main] org.quartz.impl.StdSchedulerFactory : Quartz scheduler version: 2.3.22021-10-01 13:09:00.879 INFO 38484 --- [ main] org.quartz.core.QuartzScheduler : JobFactory set to: org.springframework.scheduling.quartz.SpringBeanJobFactory@6075b2d32021-10-01 13:09:00.922 INFO 38484 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''2021-10-01 13:09:00.923 INFO 38484 --- [ main] o.s.s.quartz.SchedulerFactoryBean : Starting Quartz Scheduler now2021-10-01 13:09:00.923 INFO 38484 --- [ main] org.quartz.core.QuartzScheduler : Scheduler quartzScheduler_$_NON_CLUSTERED started.2021-10-01 13:09:00.933 INFO 38484 --- [ main] tech.pdai.springboot.quartz.App : Started App in 2.64 seconds (JVM running for 3.621)2021-10-01 13:09:00.931 INFO 38484 --- [eduler_Worker-1] t.pdai.springboot.quartz.job.HelloJob : param, key:msg, value:Hello Quartz2021-10-01 13:09:00.933 INFO 38484 --- [eduler_Worker-1] t.pdai.springboot.quartz.job.HelloJob : Hello Job执行工夫: Wed Oct 27 13:09:00 CST 20212021-10-01 13:09:01.001 INFO 38484 --- [eduler_Worker-2] t.pdai.springboot.quartz.job.HelloJob : param, key:msg, value:Hello Quartz2021-10-01 13:09:01.001 INFO 38484 --- [eduler_Worker-2] t.pdai.springboot.quartz.job.HelloJob : Hello Job执行工夫: Wed Oct 27 13:09:01 CST 20212021-10-01 13:09:02.000 INFO 38484 --- [eduler_Worker-3] t.pdai.springboot.quartz.job.HelloJob : param, key:msg, value:Hello Quartz2021-10-01 13:09:02.000 INFO 38484 --- [eduler_Worker-3] t.pdai.springboot.quartz.job.HelloJob : Hello Job执行工夫: Wed Oct 27 13:09:02 CST 20212021-10-01 13:09:03.000 INFO 38484 --- [eduler_Worker-4] t.pdai.springboot.quartz.job.HelloJob : param, key:msg, value:Hello Quartz2021-10-01 13:09:03.001 INFO 38484 --- [eduler_Worker-4] t.pdai.springboot.quartz.job.HelloJob : Hello Job执行工夫: Wed Oct 27 13:09:03 CST 20212021-10-01 13:09:04.001 INFO 38484 --- [eduler_Worker-5] t.pdai.springboot.quartz.job.HelloJob : param, key:msg, value:Hello Quartz2021-10-01 13:09:04.001 INFO 38484 --- [eduler_Worker-5] t.pdai.springboot.quartz.job.HelloJob : Hello Job执行工夫: Wed Oct 27 13:09:04 CST 20212021-10-01 13:09:05.002 INFO 38484 --- [eduler_Worker-6] t.pdai.springboot.quartz.job.HelloJob : param, key:msg, value:Hello Quartz2021-10-01 13:09:05.003 INFO 38484 --- [eduler_Worker-6] t.pdai.springboot.quartz.job.HelloJob : Hello Job执行工夫: Wed Oct 27 13:09:05 CST 20212021-10-01 13:09:06.000 INFO 38484 --- [eduler_Worker-7] t.pdai.springboot.quartz.job.HelloJob : param, key:msg, value:Hello Quartz2021-10-01 13:09:06.001 INFO 38484 --- [eduler_Worker-7] t.pdai.springboot.quartz.job.HelloJob : Hello Job执行工夫: Wed Oct 27 13:09:06 CST 20212021-10-01 13:09:07.002 INFO 38484 --- [eduler_Worker-8] t.pdai.springboot.quartz.job.HelloJob : param, key:msg, value:Hello Quartz2021-10-01 13:09:07.002 INFO 38484 --- [eduler_Worker-8] t.pdai.springboot.quartz.job.HelloJob : Hello Job执行工夫: Wed Oct 27 13:09:07 CST 20212021-10-01 13:09:08.002 INFO 38484 --- [eduler_Worker-9] t.pdai.springboot.quartz.job.HelloJob : param, key:msg, value:Hello Quartz2021-10-01 13:09:08.003 INFO 38484 --- [eduler_Worker-9] t.pdai.springboot.quartz.job.HelloJob : Hello Job执行工夫: Wed Oct 27 13:09:08 CST 20212021-10-01 13:09:09.000 INFO 38484 --- [duler_Worker-10] t.pdai.springboot.quartz.job.HelloJob : param, key:msg, value:Hello Quartz2021-10-01 13:09:09.000 INFO 38484 --- [duler_Worker-10] t.pdai.springboot.quartz.job.HelloJob : Hello Job执行工夫: Wed Oct 27 13:09:09 CST 20212021-10-01 13:09:10.001 INFO 38484 --- [eduler_Worker-1] t.pdai.springboot.quartz.job.HelloJob : param, key:msg, value:Hello Quartz2021-10-01 13:09:10.002 INFO 38484 --- [eduler_Worker-1] t.pdai.springboot.quartz.job.HelloJob : Hello Job执行工夫: Wed Oct 27 13:09:10 CST 20212021-10-01 13:09:11.014 INFO 38484 --- [eduler_Worker-2] t.pdai.springboot.quartz.job.HelloJob : param, key:msg, value:Hello Quartz2021-10-01 13:09:11.014 INFO 38484 --- [eduler_Worker-2] t.pdai.springboot.quartz.job.HelloJob : Hello Job执行工夫: Wed Oct 27 13:09:11 CST 2021
实现案例 - 分布式形式
本例将展现quartz实现基于数据库的分布式工作治理,和管制job生命周期。
整体我的项目构造如下:
后端实现
- pom.xml
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.5.3</version> <relativePath/> <!-- lookup parent from repository --> </parent> <modelVersion>4.0.0</modelVersion> <groupId>tech.pdai</groupId> <artifactId>423-springboot-demo-schedule-quartz-cluster</artifactId> <version>1.0-SNAPSHOT</version> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-quartz</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.42</version><!--$NO-MVN-MAN-VER$--> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.20</version> <optional>true</optional> </dependency> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>5.0.0</version> </dependency> </dependencies></project>
- 创立Schema
须要提前在MySQL中创立schema: quartz_jobs
# 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;# DROP TABLE IF EXISTS QRTZ_TASK_HISTORY;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 TABLE QRTZ_TASK_HISTORY ( SCHED_NAME VARCHAR(120) NOT NULL, INSTANCE_ID VARCHAR(200) NOT NULL, FIRE_ID VARCHAR(95) NOT NULL, TASK_NAME VARCHAR(200) NULL, TASK_GROUP VARCHAR(200) NULL, FIRED_TIME BIGINT(13) NULL, FIRED_WAY VARCHAR(8) NULL, COMPLETE_TIME BIGINT(13) NULL, EXPEND_TIME BIGINT(13) NULL, REFIRED INT NULL, EXEC_STATE VARCHAR(10) NULL, LOG TEXT NULL, PRIMARY KEY (FIRE_ID))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);CREATE INDEX IDX_QRTZ_TK_S ON QRTZ_TASK_HISTORY(SCHED_NAME);commit;
- application.yml
spring: datasource: url: jdbc:mysql://localhost:3306/quartz_jobs?useUnicode=true&useSSL=false username: root password: xxxxxxxx driver-class-name: com.mysql.jdbc.Driver quartz: #相干属性配置 properties: org: quartz: scheduler: instanceName: clusteredScheduler instanceId: AUTO jobStore: class: org.quartz.impl.jdbcjobstore.JobStoreTX driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate tablePrefix: QRTZ_ isClustered: true clusterCheckinInterval: 10000 useProperties: false threadPool: class: org.quartz.simpl.SimpleThreadPool threadCount: 10 threadPriority: 5 threadsInheritContextClassLoaderOfInitializingThread: true #数据库形式 job-store-type: jdbc
- 定义JobDetails实体
/** * @author pdai * */@Datapublic class JobDetails{ private String cronExpression; private String jobClassName; private String triggerGroupName; private String triggerName; private String jobGroupName; private String jobName; private Date nextFireTime; private Date previousFireTime; private Date startTime; private String timeZone; private String status;}
- Job治理类
package tech.pdai.springboot.quartz.cluster.manager;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;import java.util.Set;import com.github.pagehelper.PageHelper;import com.github.pagehelper.PageInfo;import org.quartz.CronScheduleBuilder;import org.quartz.CronTrigger;import org.quartz.DateBuilder;import org.quartz.DateBuilder.IntervalUnit;import org.quartz.Job;import org.quartz.JobBuilder;import org.quartz.JobDetail;import org.quartz.JobExecutionContext;import org.quartz.JobKey;import org.quartz.Scheduler;import org.quartz.SchedulerException;import org.quartz.SimpleScheduleBuilder;import org.quartz.Trigger;import org.quartz.TriggerBuilder;import org.quartz.TriggerKey;import org.quartz.impl.matchers.GroupMatcher;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.scheduling.quartz.QuartzJobBean;import org.springframework.stereotype.Component;import tech.pdai.springboot.quartz.cluster.entity.JobDetails;/** * @author pdai */@Componentpublic class QuartzManager { @Autowired private Scheduler sched; /** * 创立or更新工作,存在则更新不存在创立 * * @param jobClass 工作类 * @param jobName 工作名称 * @param jobGroupName 工作组名称 * @param jobCron cron表达式 */ public void addOrUpdateJob(Class<? extends QuartzJobBean> jobClass, String jobName, String jobGroupName, String jobCron) { try { TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroupName); CronTrigger trigger = (CronTrigger) sched.getTrigger(triggerKey); if (trigger==null) { addJob(jobClass, jobName, jobGroupName, jobCron); } else { if (trigger.getCronExpression().equals(jobCron)) { return; } updateJob(jobName, jobGroupName, jobCron); } } catch (SchedulerException e) { e.printStackTrace(); } } /** * 减少一个job * * @param jobClass 工作实现类 * @param jobName 工作名称 * @param jobGroupName 工作组名 * @param jobCron cron表达式(如:0/5 * * * * ? ) */ public void addJob(Class<? extends QuartzJobBean> jobClass, String jobName, String jobGroupName, String jobCron) { try { JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(jobName, jobGroupName).build(); Trigger trigger = TriggerBuilder.newTrigger().withIdentity(jobName, jobGroupName) .startAt(DateBuilder.futureDate(1, IntervalUnit.SECOND)) .withSchedule(CronScheduleBuilder.cronSchedule(jobCron)).startNow().build(); sched.scheduleJob(jobDetail, trigger); if (!sched.isShutdown()) { sched.start(); } } catch (SchedulerException e) { e.printStackTrace(); } } /** * @param jobClass * @param jobName * @param jobGroupName * @param jobTime */ public void addJob(Class<? extends Job> jobClass, String jobName, String jobGroupName, int jobTime) { addJob(jobClass, jobName, jobGroupName, jobTime, -1); } public void addJob(Class<? extends Job> jobClass, String jobName, String jobGroupName, int jobTime, int jobTimes) { try { JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(jobName, jobGroupName)// 工作名称和组形成工作key .build(); // 应用simpleTrigger规定 Trigger trigger; if (jobTimes < 0) { trigger = TriggerBuilder.newTrigger().withIdentity(jobName, jobGroupName) .withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(1).withIntervalInSeconds(jobTime)) .startNow().build(); } else { trigger = TriggerBuilder .newTrigger().withIdentity(jobName, jobGroupName).withSchedule(SimpleScheduleBuilder .repeatSecondlyForever(1).withIntervalInSeconds(jobTime).withRepeatCount(jobTimes)) .startNow().build(); } sched.scheduleJob(jobDetail, trigger); if (!sched.isShutdown()) { sched.start(); } } catch (SchedulerException e) { e.printStackTrace(); } } public void updateJob(String jobName, String jobGroupName, String jobTime) { try { TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroupName); CronTrigger trigger = (CronTrigger) sched.getTrigger(triggerKey); trigger = trigger.getTriggerBuilder().withIdentity(triggerKey) .withSchedule(CronScheduleBuilder.cronSchedule(jobTime)).build(); // 重启触发器 sched.rescheduleJob(triggerKey, trigger); } catch (SchedulerException e) { e.printStackTrace(); } } /** * 删除工作一个job * * @param jobName 工作名称 * @param jobGroupName 工作组名 */ public void deleteJob(String jobName, String jobGroupName) { try { sched.pauseTrigger(TriggerKey.triggerKey(jobName, jobGroupName)); sched.unscheduleJob(TriggerKey.triggerKey(jobName, jobGroupName)); sched.deleteJob(new JobKey(jobName, jobGroupName)); } catch (Exception e) { e.printStackTrace(); } } /** * 暂停一个job * * @param jobName * @param jobGroupName */ public void pauseJob(String jobName, String jobGroupName) { try { JobKey jobKey = JobKey.jobKey(jobName, jobGroupName); sched.pauseJob(jobKey); } catch (SchedulerException e) { e.printStackTrace(); } } /** * 复原一个job * * @param jobName * @param jobGroupName */ public void resumeJob(String jobName, String jobGroupName) { try { JobKey jobKey = JobKey.jobKey(jobName, jobGroupName); sched.resumeJob(jobKey); } catch (SchedulerException e) { e.printStackTrace(); } } /** * 立刻执行一个job * * @param jobName * @param jobGroupName */ public void runAJobNow(String jobName, String jobGroupName) { try { JobKey jobKey = JobKey.jobKey(jobName, jobGroupName); sched.triggerJob(jobKey); } catch (SchedulerException e) { e.printStackTrace(); } } public PageInfo<JobDetails> queryAllJobBean(int pageNum, int pageSize) { PageHelper.startPage(pageNum, pageSize); List<JobDetails> jobList = null; try { GroupMatcher<JobKey> matcher = GroupMatcher.anyJobGroup(); Set<JobKey> jobKeys = sched.getJobKeys(matcher); jobList = new ArrayList<>(); for (JobKey jobKey : jobKeys) { List<? extends Trigger> triggers = sched.getTriggersOfJob(jobKey); for (Trigger trigger : triggers) { JobDetails jobDetails = new JobDetails(); if (trigger instanceof CronTrigger) { CronTrigger cronTrigger = (CronTrigger) trigger; jobDetails.setCronExpression(cronTrigger.getCronExpression()); jobDetails.setTimeZone(cronTrigger.getTimeZone().getDisplayName()); } jobDetails.setTriggerGroupName(trigger.getKey().getName()); jobDetails.setTriggerName(trigger.getKey().getGroup()); jobDetails.setJobGroupName(jobKey.getGroup()); jobDetails.setJobName(jobKey.getName()); jobDetails.setStartTime(trigger.getStartTime()); jobDetails.setJobClassName(sched.getJobDetail(jobKey).getJobClass().getName()); jobDetails.setNextFireTime(trigger.getNextFireTime()); jobDetails.setPreviousFireTime(trigger.getPreviousFireTime()); jobDetails.setStatus(sched.getTriggerState(trigger.getKey()).name()); jobList.add(jobDetails); } } } catch (SchedulerException e) { e.printStackTrace(); } return new PageInfo<>(jobList); } /** * 获取所有打算中的工作列表 * * @return */ public List<Map<String, Object>> queryAllJob() { List<Map<String, Object>> jobList = null; try { GroupMatcher<JobKey> matcher = GroupMatcher.anyJobGroup(); Set<JobKey> jobKeys = sched.getJobKeys(matcher); jobList = new ArrayList<>(); for (JobKey jobKey : jobKeys) { List<? extends Trigger> triggers = sched.getTriggersOfJob(jobKey); for (Trigger trigger : triggers) { Map<String, Object> map = new HashMap<>(); map.put("jobName", jobKey.getName()); map.put("jobGroupName", jobKey.getGroup()); map.put("description", "trigger:" + trigger.getKey()); Trigger.TriggerState triggerState = sched.getTriggerState(trigger.getKey()); map.put("jobStatus", triggerState.name()); if (trigger instanceof CronTrigger) { CronTrigger cronTrigger = (CronTrigger) trigger; String cronExpression = cronTrigger.getCronExpression(); map.put("jobTime", cronExpression); } jobList.add(map); } } } catch (SchedulerException e) { e.printStackTrace(); } return jobList; } /** * 获取所有正在运行的job * * @return */ public List<Map<String, Object>> queryRunJon() { List<Map<String, Object>> jobList = null; try { List<JobExecutionContext> executingJobs = sched.getCurrentlyExecutingJobs(); jobList = new ArrayList<>(executingJobs.size()); for (JobExecutionContext executingJob : executingJobs) { Map<String, Object> map = new HashMap<>(); JobDetail jobDetail = executingJob.getJobDetail(); JobKey jobKey = jobDetail.getKey(); Trigger trigger = executingJob.getTrigger(); map.put("jobName", jobKey.getName()); map.put("jobGroupName", jobKey.getGroup()); map.put("description", "trigger:" + trigger.getKey()); Trigger.TriggerState triggerState = sched.getTriggerState(trigger.getKey()); map.put("jobStatus", triggerState.name()); if (trigger instanceof CronTrigger) { CronTrigger cronTrigger = (CronTrigger) trigger; String cronExpression = cronTrigger.getCronExpression(); map.put("jobTime", cronExpression); } jobList.add(map); } } catch (SchedulerException e) { e.printStackTrace(); } return jobList; }}
- Job控制器接口
package tech.pdai.springboot.quartz.cluster.controller;import java.util.HashMap;import java.util.Map;import com.github.pagehelper.PageInfo;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.scheduling.quartz.QuartzJobBean;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.RestController;import tech.pdai.springboot.quartz.cluster.entity.JobDetails;import tech.pdai.springboot.quartz.cluster.manager.QuartzManager;/** * @author pdai */@RestController@RequestMapping(value = "/job")public class JobController { @Autowired private QuartzManager qtzManager; @SuppressWarnings("unchecked") private static Class<? extends QuartzJobBean> getClass(String classname) throws Exception { Class<?> class1 = Class.forName(classname); return (Class<? extends QuartzJobBean>) class1; } /** * @param jobClassName * @param jobGroupName * @param cronExpression * @throws Exception */ @PostMapping(value = "/addjob") public void addjob(@RequestParam(value = "jobClassName") String jobClassName, @RequestParam(value = "jobGroupName") String jobGroupName, @RequestParam(value = "cronExpression") String cronExpression) throws Exception { qtzManager.addOrUpdateJob(getClass(jobClassName), jobClassName, jobGroupName, cronExpression); } /** * @param jobClassName * @param jobGroupName * @throws Exception */ @PostMapping(value = "/pausejob") public void pausejob(@RequestParam(value = "jobClassName") String jobClassName, @RequestParam(value = "jobGroupName") String jobGroupName) throws Exception { qtzManager.pauseJob(jobClassName, jobGroupName); } /** * @param jobClassName * @param jobGroupName * @throws Exception */ @PostMapping(value = "/resumejob") public void resumejob(@RequestParam(value = "jobClassName") String jobClassName, @RequestParam(value = "jobGroupName") String jobGroupName) throws Exception { qtzManager.resumeJob(jobClassName, jobGroupName); } /** * @param jobClassName * @param jobGroupName * @param cronExpression * @throws Exception */ @PostMapping(value = "/reschedulejob") public void rescheduleJob(@RequestParam(value = "jobClassName") String jobClassName, @RequestParam(value = "jobGroupName") String jobGroupName, @RequestParam(value = "cronExpression") String cronExpression) throws Exception { qtzManager.addOrUpdateJob(getClass(jobClassName), jobClassName, jobGroupName, cronExpression); } /** * @param jobClassName * @param jobGroupName * @throws Exception */ @PostMapping(value = "/deletejob") public void deletejob(@RequestParam(value = "jobClassName") String jobClassName, @RequestParam(value = "jobGroupName") String jobGroupName) throws Exception { qtzManager.deleteJob(jobClassName, jobGroupName); } /** * @param pageNum * @param pageSize * @return */ @GetMapping(value = "/queryjob") public Map<String, Object> queryjob(@RequestParam(value = "pageNum") Integer pageNum, @RequestParam(value = "pageSize") Integer pageSize) { PageInfo<JobDetails> jobAndTrigger = qtzManager.queryAllJobBean(pageNum, pageSize); Map<String, Object> map = new HashMap<String, Object>(); map.put("JobAndTrigger", jobAndTrigger); map.put("number", jobAndTrigger.getTotal()); return map; }}
- 定义具体的Job
package tech.pdai.springboot.quartz.cluster.job;import java.util.Date;import lombok.extern.slf4j.Slf4j;import org.quartz.JobExecutionContext;import org.quartz.JobExecutionException;import org.springframework.scheduling.quartz.QuartzJobBean;@Slf4jpublic class HelloJob extends QuartzJobBean { @Override protected void executeInternal(JobExecutionContext context) throws JobExecutionException { // get parameters context.getJobDetail().getJobDataMap().forEach( (k, v) -> log.info("param, key:{}, value:{}", k, v) ); // your logics log.info("Hello Job执行工夫: " + new Date()); }}
前端实现
简略用VueJS 写个页面测试
<!DOCTYPE html><html><head><meta charset="UTF-8"> <title>QuartzDemo</title> <link rel="stylesheet" href="https://unpkg.com/element-ui@2.0.5/lib/theme-chalk/index.css"> <script src="https://unpkg.com/vue/dist/vue.js"></script> <script src="http://cdn.bootcss.com/vue-resource/1.3.4/vue-resource.js"></script> <script src="https://unpkg.com/element-ui@2.0.5/lib/index.js"></script> <style> #top { background:#20A0FF; padding:5px; overflow:hidden } </style> </head><body> <div id="test"> <div id="top"> <el-button type="text" @click="search" style="color:white">查问</el-button> <el-button type="text" @click="handleadd" style="color:white">增加</el-button> </span> </div> <br/> <div style="margin-top:15px"> <el-table ref="testTable" :data="tableData" style="width:100%" border > <el-table-column prop="status" label="工作状态" sortable show-overflow-tooltip> </el-table-column> <el-table-column prop="jobName" label="工作名称" sortable show-overflow-tooltip> </el-table-column> <el-table-column prop="jobGroupName" label="工作所在组" sortable> </el-table-column> <el-table-column prop="jobClassName" label="工作类名" sortable> </el-table-column> <el-table-column prop="triggerName" label="触发器名称" sortable> </el-table-column> <el-table-column prop="triggerGroupName" label="触发器所在组" sortable> </el-table-column> <el-table-column prop="cronExpression" label="表达式" sortable> </el-table-column> <el-table-column prop="timeZone" label="时区" sortable> </el-table-column> <el-table-column label="操作" width="300"> <template scope="scope"> <el-button size="small" type="warning" @click="handlePause(scope.$index, scope.row)">暂停</el-button> <el-button size="small" type="info" @click="handleResume(scope.$index, scope.row)">复原</el-button> <el-button size="small" type="danger" @click="handleDelete(scope.$index, scope.row)">删除</el-button> <el-button size="small" type="success" @click="handleUpdate(scope.$index, scope.row)">批改</el-button> </template> </el-table-column> </el-table> <div align="center"> <el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page="currentPage" :page-sizes="[10, 20, 30, 40]" :page-size="pagesize" layout="total, sizes, prev, pager, next, jumper" :total="totalCount"> </el-pagination> </div> </div> <el-dialog title="增加工作" :visible.sync="dialogFormVisible"> <el-form :model="form"> <el-form-item label="工作名称" label-width="120px" style="width:35%"> <el-input v-model="form.jobName" auto-complete="off"></el-input> </el-form-item> <el-form-item label="工作分组" label-width="120px" style="width:35%"> <el-input v-model="form.jobGroup" auto-complete="off"></el-input> </el-form-item> <el-form-item label="表达式" label-width="120px" style="width:35%"> <el-input v-model="form.cronExpression" auto-complete="off"></el-input> </el-form-item> </el-form> <div slot="footer" class="dialog-footer"> <el-button @click="dialogFormVisible = false">取 消</el-button> <el-button type="primary" @click="add">确 定</el-button> </div> </el-dialog> <el-dialog title="批改工作" :visible.sync="updateFormVisible"> <el-form :model="updateform"> <el-form-item label="表达式" label-width="120px" style="width:35%"> <el-input v-model="updateform.cronExpression" auto-complete="off"></el-input> </el-form-item> </el-form> <div slot="footer" class="dialog-footer"> <el-button @click="updateFormVisible = false">取 消</el-button> <el-button type="primary" @click="update">确 定</el-button> </div> </el-dialog> </div> <footer align="center"> <p>© Quartz 工作治理</p> </footer> <script> var vue = new Vue({ el:"#test", data: { //表格当前页数据 tableData: [], //申请的URL url:'job/queryjob', //默认每页数据量 pagesize: 10, //以后页码 currentPage: 1, //查问的页码 start: 1, //默认数据总数 totalCount: 1000, //增加对话框默认可见性 dialogFormVisible: false, //批改对话框默认可见性 updateFormVisible: false, //提交的表单 form: { jobName: '', jobGroup: '', cronExpression: '', }, updateform: { jobName: '', jobGroup: '', cronExpression: '', }, }, methods: { //从服务器读取数据 loadData: function(pageNum, pageSize){ this.$http.get('job/queryjob?' + 'pageNum=' + pageNum + '&pageSize=' + pageSize).then(function(res){ console.log(res) this.tableData = res.body.JobAndTrigger.list; this.totalCount = res.body.number; },function(){ console.log('failed'); }); }, //单行删除 handleDelete: function(index, row) { this.$http.post('job/deletejob',{"jobClassName":row.jobName,"jobGroupName":row.jobGroupName},{emulateJSON: true}).then(function(res){ this.loadData( this.currentPage, this.pagesize); },function(){ console.log('failed'); }); }, //暂停工作 handlePause: function(index, row){ this.$http.post('job/pausejob',{"jobClassName":row.jobName,"jobGroupName":row.jobGroupName},{emulateJSON: true}).then(function(res){ this.loadData( this.currentPage, this.pagesize); },function(){ console.log('failed'); }); }, //复原工作 handleResume: function(index, row){ this.$http.post('job/resumejob',{"jobClassName":row.jobName,"jobGroupName":row.jobGroupName},{emulateJSON: true}).then(function(res){ this.loadData( this.currentPage, this.pagesize); },function(){ console.log('failed'); }); }, //搜寻 search: function(){ this.loadData(this.currentPage, this.pagesize); }, //弹出对话框 handleadd: function(){ this.dialogFormVisible = true; }, //增加 add: function(){ this.$http.post('job/addjob',{"jobClassName":this.form.jobName,"jobGroupName":this.form.jobGroup,"cronExpression":this.form.cronExpression},{emulateJSON: true}).then(function(res){ this.loadData(this.currentPage, this.pagesize); this.dialogFormVisible = false; },function(){ console.log('failed'); }); }, //更新 handleUpdate: function(index, row){ console.log(row) this.updateFormVisible = true; this.updateform.jobName = row.jobClassName; this.updateform.jobGroup = row.jobGroupName; }, //更新工作 update: function(){ this.$http.post ('job/reschedulejob', {"jobClassName":this.updateform.jobName, "jobGroupName":this.updateform.jobGroup, "cronExpression":this.updateform.cronExpression },{emulateJSON: true} ).then(function(res){ this.loadData(this.currentPage, this.pagesize); this.updateFormVisible = false; },function(){ console.log('failed'); }); }, //每页显示数据量变更 handleSizeChange: function(val) { this.pagesize = val; this.loadData(this.currentPage, this.pagesize); }, //页码变更 handleCurrentChange: function(val) { this.currentPage = val; this.loadData(this.currentPage, this.pagesize); }, }, }); //载入数据 vue.loadData(vue.currentPage, vue.pagesize); </script> </body></html>
测试成果
(PS: 这里的工作名称须要改成你本人的残缺类名称)
展现正在运行的Jobs:
减少新的Job:
Jobs长久化在数据库:
示例源码
https://github.com/realpdai/t...
更多内容
辞别碎片化学习,无套路一站式体系化学习后端开发: Java 全栈常识体系 https://pdai.tech