原文写的不全执行代码不成功,经我修改后可以正常执行。
原文链接:https://blog.csdn.net/qq_22193961/article/details/137743746
Quartz 是一个开源的作业调度框架,它完全由 Java 写成,并设计用于 J2SE 和 J2EE 应用中。它提供了巨大的灵 活性而不牺牲简单性。你能够用它来为执行一个作业而创建简单的或复杂的调度。它有很多特征,如:数据库支持,集群,插件,EJB 作业预构 建,JavaMail 及其它,支持 cron-like 表达式等等。
quick-start
三个核心类
-
JobDetail 任务类
-
Trigger 触发器
-
Scheduler 调度器
Quartz集群原理
Quartz的集群部署方案是分布式的,没有负责集中管理的节点,而是利用数据库行锁的方式来实现集群环境下的并发控制。
向Mysql获取行锁的语句:
select * from {0}LOCKS where sched_name = ? and lock_name = ? for update
{0}会替换为配置文件默认配置的QRTZ_。sched_name为应用集群的实例名,lock_name就是行级锁名。Quartz主要由两个行级锁。
lock_name desc
STATE_ACCESS 状态访问锁
TRIGGER_ACCESS 触发器访问锁
同一集群下,instanceName必须相同,instanceId可自动生成,isClustered为true,持久化存储。
总结:
数据库存储:Quartz集群使用一个共享的数据库来存储任务和调度信息。每个Quartz实例都连接到同一个数据库,并共享这些任务和调度信息,以确保任务的一致性和可靠性。
选举机制:在Quartz集群中,每个实例都有一个唯一的标识符,称为实例ID。当一个Quartz实例启动时,它会尝试成为集群的主节点。如果没有当前的主节点,那么该实例将成为主节点。如果已经有主节点存在,那么该实例将成为备用节点,并等待主节点故障时接管。
心跳检测:每个Quartz实例都定期发送心跳信号给其他实例,以保持集群的健康状态。如果一个实例在一段时间内没有收到其他实例的心跳信号,那么它会认为主节点已经故障,并尝试成为新的主节点。
Quartz数据库表简介
Quartz集群依赖于数据库,所以必须首先创建Quartz数据库表。Quartz发布包中包括了所有被支持的数据库平台的SQL脚本,版本:2.3.0, SQL文件是在这个路径下:quartz-2.3.0-SNAPSHOT\src\org\quartz\impl\jdbcjobstore\tables_mysql.sql,总共11张表。
如果不愿意找MySQL脚本看下面
#
# Quartz seems to work best with the driver mm.mysql-2.0.7-bin.jar
#
# In your Quartz properties file, you'll need to set
# org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#
DROP TABLE IF EXISTS QRTZ_JOB_LISTENERS;
DROP TABLE IF EXISTS QRTZ_TRIGGER_LISTENERS;
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_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
(
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_VOLATILE VARCHAR(1) NOT NULL,
IS_STATEFUL VARCHAR(1) NOT NULL,
REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
JOB_DATA BLOB NULL,
PRIMARY KEY (JOB_NAME,JOB_GROUP)
);
CREATE TABLE QRTZ_JOB_LISTENERS
(
JOB_NAME VARCHAR(200) NOT NULL,
JOB_GROUP VARCHAR(200) NOT NULL,
JOB_LISTENER VARCHAR(200) NOT NULL,
PRIMARY KEY (JOB_NAME,JOB_GROUP,JOB_LISTENER),
FOREIGN KEY (JOB_NAME,JOB_GROUP)
REFERENCES QRTZ_JOB_DETAILS(JOB_NAME,JOB_GROUP)
);
CREATE TABLE QRTZ_TRIGGERS
(
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
JOB_NAME VARCHAR(200) NOT NULL,
JOB_GROUP VARCHAR(200) NOT NULL,
IS_VOLATILE VARCHAR(1) 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 (TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (JOB_NAME,JOB_GROUP)
REFERENCES QRTZ_JOB_DETAILS(JOB_NAME,JOB_GROUP)
);
CREATE TABLE QRTZ_SIMPLE_TRIGGERS
(
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 (TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(TRIGGER_NAME,TRIGGER_GROUP)
);
CREATE TABLE QRTZ_CRON_TRIGGERS
(
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
CRON_EXPRESSION VARCHAR(200) NOT NULL,
TIME_ZONE_ID VARCHAR(80),
PRIMARY KEY (TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(TRIGGER_NAME,TRIGGER_GROUP)
);
CREATE TABLE QRTZ_BLOB_TRIGGERS
(
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
BLOB_DATA BLOB NULL,
PRIMARY KEY (TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(TRIGGER_NAME,TRIGGER_GROUP)
);
CREATE TABLE QRTZ_TRIGGER_LISTENERS
(
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
TRIGGER_LISTENER VARCHAR(200) NOT NULL,
PRIMARY KEY (TRIGGER_NAME,TRIGGER_GROUP,TRIGGER_LISTENER),
FOREIGN KEY (TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(TRIGGER_NAME,TRIGGER_GROUP)
);
CREATE TABLE QRTZ_CALENDARS
(
CALENDAR_NAME VARCHAR(200) NOT NULL,
CALENDAR BLOB NOT NULL,
PRIMARY KEY (CALENDAR_NAME)
);
CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS
(
TRIGGER_GROUP VARCHAR(200) NOT NULL,
PRIMARY KEY (TRIGGER_GROUP)
);
CREATE TABLE QRTZ_FIRED_TRIGGERS
(
ENTRY_ID VARCHAR(95) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
IS_VOLATILE VARCHAR(1) NOT NULL,
INSTANCE_NAME VARCHAR(200) NOT NULL,
FIRED_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_STATEFUL VARCHAR(1) NULL,
REQUESTS_RECOVERY VARCHAR(1) NULL,
PRIMARY KEY (ENTRY_ID)
);
CREATE TABLE QRTZ_SCHEDULER_STATE
(
INSTANCE_NAME VARCHAR(200) NOT NULL,
LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
CHECKIN_INTERVAL BIGINT(13) NOT NULL,
PRIMARY KEY (INSTANCE_NAME)
);
CREATE TABLE QRTZ_LOCKS
(
LOCK_NAME VARCHAR(40) NOT NULL,
PRIMARY KEY (LOCK_NAME)
);
INSERT INTO QRTZ_LOCKS values('TRIGGER_ACCESS');
INSERT INTO QRTZ_LOCKS values('JOB_ACCESS');
INSERT INTO QRTZ_LOCKS values('CALENDAR_ACCESS');
INSERT INTO QRTZ_LOCKS values('STATE_ACCESS');
INSERT INTO QRTZ_LOCKS values('MISFIRE_ACCESS');
commit;
Quartz集群实现方式
引入依赖
<?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"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.9</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>org.example</groupId> <artifactId>demo</artifactId> <version>1.0-SNAPSHOT</version> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <mysql.driver.version>8.0.13</mysql.driver.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-quartz</artifactId> </dependency> <!-- mysql --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql.driver.version}</version> <scope>runtime</scope> </dependency> <!-- orm --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <source>1.8</source> <target>1.8</target> <encoding>UTF-8</encoding> </configuration> </plugin> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>3.0.3</version> <configuration> <mainClass>com.example.demo.DemoApplication</mainClass> <skip>true</skip> </configuration> <executions> <execution> <id>repackage</id> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </project>
application.properties
# 应用服务 WEB 访问端口 server.port=18080 spring.datasource.url=jdbc:mysql://10.146.4.28:3306/yizhuang?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC spring.datasource.username=root spring.datasource.password=uEyeT231$jd#2@R spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.max-active=1000 spring.datasource.max-idle=20 spring.datasource.min-idle=5 spring.datasource.initial-size=10
spring-quartz.properties
#============================================================================ # 配置JobStore #============================================================================ # JobDataMaps是否都为String类型,默认false # 若是true的话,便可不用让更复杂的对象以序列化的形式保存到BLOB列中 org.quartz.jobStore.useProperties=false # 存储相关信息表的前缀,默认QRTZ_ org.quartz.jobStore.tablePrefix = QRTZ_ # 是否加入集群 #是否是应用在集群中,当应用在集群中时必须设置为TRUE,否则会出错。 #如果有多个Quartz实例在用同一套数据库时,必须设置为true。 org.quartz.jobStore.isClustered = true # 调度实例失效的检查时间间隔 ms #只用于设置了isClustered为true的时候,设置一个频度(毫秒),用于实例报告给集群中的其他实例。 #这会影响到侦测失败实例的敏捷度。 org.quartz.jobStore.clusterCheckinInterval = 5000 # 当设置为“true”时,此属性告诉Quartz 在非托管JDBC连接上调用setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED)。 org.quartz.jobStore.txIsolationLevelReadCommitted = true # 数据保存方式为数据库持久化,选择JDBC的存储方式 org.quartz.jobStore.class=org.springframework.scheduling.quartz.LocalDataSourceJobStore # 数据库代理类,一般org.quartz.impl.jdbcjobstore.StdJDBCDelegate可以满足大部分数据库 org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate #这个值必须datasource的配置信息 org.quartz.jobStore.dataSource = myDataSource #最大能忍受的触发超时时间,如果超时则认为“失误” org.quartz.jobStore.misfireThreshold = 60000 #这是JobStore能处理的错过触发的Trigger的最大数量。处理太多(2打)很快就会导致数据库表被锁定够长的时间, #这样会妨碍别的(还未错过触发)trigger执行的性能。 org.quartz.jobStore.maxMisfiresToHandleAtATime=20 #设置这个参数为true会告诉Quartz从数据源获取连接后不要调用它的setAutoCommit(false)方法。 #在少数情况下是有用的,比如有一个驱动本来是关闭的,但是又调用这个关闭的方法。但是大部分情况下驱动都要求调用setAutoCommit(false) org.quartz.jobStore.dontSetAutoCommitFalse=false #这必须是一个从LOCKS表查询一行并对这行记录加锁的SQL。假设没有设置,默认值如下。 #{0}会在运行期间被前面配置的TABLE_PREFIX所代替 org.quartz.jobStore.selectWithLockSQL=SELECT * FROM {0}LOCKS WHERE LOCK_NAME = ? FOR UPDATE #值为true时告知Quartz(当使用JobStoreTX或CMT)调用JDBC连接的setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE) 方法。这有助于某些数据库在高负载和长时间事务时锁的超时。 org.quartz.jobStore.txIsolationLevelSerializable=false #============================================================================ # Scheduler 调度器属性配置 #============================================================================ # 调度标识名 集群中每一个实例都必须使用相同的名称,可以为任意字符串,对于scheduler来说此值没有意义,但是可以区分同一系统中多个不同的实例 org.quartz.scheduler.instanceName = ClusterQuartz # ID设置为自动获取 每一个必须不同 org.quartz.scheduler.instanceId= AUTO org.quartz.scheduler.rmi.export = false org.quartz.scheduler.rmi.proxy = false # 默认false,若是在执行Job之前Quartz开启UserTransaction,此属性应该为true。 #Job执行完毕,JobDataMap更新完(如果是StatefulJob)事务就会提交。默认值是false,可以在job类上使用@ExecuteInJTATransaction注解, # 以便在各自的job上决定是否开启JTA事务。 org.quartz.scheduler.wrapJobExecutionInUserTransaction = false #一个scheduler节点允许接收的trigger的最大数,默认是1,这个值越大,定时任务执行的越多,但代价是集群节点之间的不均衡。 org.quartz.scheduler.batchTriggerAcquisitionMaxCount=1 #============================================================================ # 配置ThreadPool #============================================================================ # 线程池的实现类(一般使用SimpleThreadPool即可满足几乎所有用户的需求) org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool # 指定线程数,一般设置为1-100直接的整数,根据系统资源配置 org.quartz.threadPool.threadCount = 10 # 设置线程的优先级(可以是Thread.MIN_PRIORITY(即1)和Thread.MAX_PRIORITY(这是10)之间的任何int 。默认值为Thread.NORM_PRIORITY(5)。) org.quartz.threadPool.threadPriority = 5 #加载任务代码的ClassLoader是否从外部继承 org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread= true #是否设置调度器线程为守护线程 org.quartz.scheduler.makeSchedulerThreadDaemon=true
QuartzJob.java
import org.quartz.DisallowConcurrentExecution; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.PersistJobDataAfterExecution; import org.quartz.SchedulerException; import org.springframework.scheduling.quartz.QuartzJobBean; import java.util.Date; /** * @description: * @create: 2024-04-13 22:58 **/ @PersistJobDataAfterExecution @DisallowConcurrentExecution public class QuartzJob extends QuartzJobBean { @Override protected void executeInternal(JobExecutionContext context) throws JobExecutionException { try { Thread.sleep(2000); System.out.println(context.getScheduler().getSchedulerInstanceId()); System.out.println("taskName=" + context.getJobDetail().getKey().getName()); System.out.println("执行时间:" + new Date()); } catch (InterruptedException e) { e.printStackTrace(); } catch (SchedulerException e) { e.printStackTrace(); } } }
SchedulerConfig.java
import org.quartz.Scheduler; import org.springframework.beans.factory.annotation.Autowired; 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; /** * @description: 调度器配置 * @author: 黎剑 * @create: 2024-04-13 23:04 **/ @Configuration public class SchedulerConfig { @Autowired private DataSource dataSource; @Bean public Scheduler scheduler() throws IOException { return schedulerFactoryBean().getScheduler(); } @Bean public SchedulerFactoryBean schedulerFactoryBean() throws IOException { SchedulerFactoryBean factory = new SchedulerFactoryBean(); factory.setSchedulerName("cluster_scheduler1"); // 注入数据源 factory.setDataSource(dataSource); // 选填 factory.setApplicationContextSchedulerContextKey("application"); factory.setQuartzProperties(quartzProperties()); // 读线程池配置 factory.setTaskExecutor(schedulerThreadPool()); // 等待设置其他属性 return factory; } @Bean public Properties quartzProperties() throws IOException { PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean(); propertiesFactoryBean.setLocation(new ClassPathResource("/spring-quartz.properties")); propertiesFactoryBean.afterPropertiesSet(); return propertiesFactoryBean.getObject(); } @Bean public Executor schedulerThreadPool() { ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor(); // 处理器的核心数 taskExecutor.setCorePoolSize(Runtime.getRuntime().availableProcessors()); // 最大线程数 taskExecutor.setMaxPoolSize(Runtime.getRuntime().availableProcessors()); // 容量 taskExecutor.setQueueCapacity(Runtime.getRuntime().availableProcessors()); return taskExecutor; } }
StartApplicationListener.java
import org.quartz.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.stereotype.Component; import java.util.Date; /** * @description: * @author: 黎剑 * @create: 2024-04-13 23:23 **/ @Component public class StartApplicationListener implements ApplicationListener<ContextRefreshedEvent> { @Autowired private Scheduler scheduler; @Override public void onApplicationEvent(ContextRefreshedEvent event) { // 开启调度 // 可以先判断触发器是否存在 /** * JobDetail有一个类型为JobKey的重要属性key,相当于是该任务的键值,JobDetail注册到任务调度器Schedule中的时候,key值不允许重复。***整个任务调度过程中,Quartz都是通过Jobkey来唯一识别JobDetail的。试图将重复键值的JobDetail注册到任务调度器中而不指定覆盖的话,是不被允许的。*** * * JobKey可以通过JobBuiler的withIdentity方法指定,该方法接收name或name+group参数,从而唯一确定一个任务JobDetail。 * * 如果在JobDetail创建过程中不指定JobKey的话,Quartz会通过UUID的方式为该任务生成一个唯一的key值。 * * ***所以,同一个Job实现类(也就是同一个任务),可以通过不同的JobKey值注册到任务调度器中、绑定不同的触发器执行! */ TriggerKey triggerKey = TriggerKey.triggerKey("trigger1", "group1"); try { Trigger trigger = scheduler.getTrigger(triggerKey); if (trigger == null) { Date date=new Date(); // 触发器 CronTrigger trigger1=(CronTrigger)TriggerBuilder.newTrigger().withIdentity(triggerKey) .startAt(date) .withSchedule(CronScheduleBuilder.cronSchedule("0/10 * * * * ?")) .build(); // JobDetail jobDetail = JobBuilder.newJob(QuartzJob.class) .withIdentity("job1", "group1") .build(); scheduler.scheduleJob(jobDetail, trigger1); scheduler.start(); } } catch (SchedulerException e) { e.printStackTrace(); } } }