定时任务之Quartz使用
Quartz是一个用Java开发的开源定时任务框架。
Quartz的核心对象如下:
Quartz中的概念介绍
JobDetail
- JobDetail就是作业详细信息,包含作业名称、作业组名称、作业描述、具体作业实现类型等信息
- JobDetail有一个唯一的名称(name)和组名(group),它们共同构成了作业在调度器中的唯一标识,通过这种方式,用户可以对不同的作业进行组织和分类
- JobDetail必须关联一个实现了Job接口的类,该类封装了作业的实际执行逻辑,通常使用JobBuilder的ofClass方法来指定关联的Job
- JobDetail中有一个JobDataMap属性,使用键值对存储数据,这些数据会在作业执行时传递给关联的Job实例,可用传递运行时上下文所需要的数据
Job
- 任务的执行逻辑,任何需要在Quartz中执行的任务都需要实现Job接口
- Job接口中只有一个方法:void execute(JobExecutionContext context) throws JobExecutionException
- execute 方法是每个Job 类必须实现的核心逻辑,当作业被触发并分配给工作线程执行时,该方法会被调用
- JobExecutionContext 参数提供了与作业执行相关的上下文信息,包括触发作业的 Trigger、作业自身的 JobDetail(含 JobDataMap)、调度器的Scheduler等
- 如果作业的执行时间很长,但是每个作业被触发的间隔时间很短,有可能会出现并发执行的情况;可以通过注解设置并发策略
- @DisallowConcurrentExecution:标记在 Job 类上,指示调度器不应在同一时刻并发执行同一个作业实例
- @PersistJobDataAfterExecution:标记在 Job 类上,表示作业成功执行后,应将修改过的 JobDataMap 数据持久化保存
Trigger
-
触发器,定义Job的触发时机、重复频率以及其它的调度相关的细节;Trigger 必须与一个已定义的 JobDetail 关联,表明它将触发哪个作业的执行
-
SimpleTrigger:用于安排一次性或重复一定次数的简单调度任务;SimpleTrigger的主要属性如下
- startTime:触发器首次生效的时间点
- endTime:触发器失效的时间点(可选),超过此时间则不再触发作业
- repeatCount: 重复执行的次数(SimpleTrigger),0 表示只执行一次,-1 或 RepeatIndefinitely 表示无限次重复
- repeatInterval: 重复执行之间的间隔时间(单位通常是毫秒,SimpleTrigger)
-
CronTrigger: 基于 Cron 表达式的复杂调度触发器,支持按照诸如分钟、小时、天、月、周等周期性模式来触发作业。CronTrigger 提供了极大的灵活性,适用于各种复杂的定时任务场景,如每周一至周五的某个时段执行、每月最后一个工作日执行等
-
Trigger 有其独立的生命周期,可以被暂停、恢复、删除,也可以动态修改其触发规则
-
Misfire Instructions:
- 当调度器因故未能在预期时间触发作业时(称为 Misfire),可以通过设置 Misfire Instructions 指定在这种情况下应该如何处理。例如,可以选择立即执行、跳过此次执行、按原计划下一次触发时间执行等
Scheduler
- Scheduler是Quartz框架的核心组件,它负责管理和调度所有Job及其对应的触发器,主要职责包括:
- 注册和管理Job:Scheduler 维护一个内部的 Job 注册表,用于存储所有已定义的 JobDetail 对象。提供 API 用于添加、修改、查询和删除 JobDetail
- 调度 Triggers:根据关联的 Trigger 规则,决定何时触发相应的 Job 执行。处理 Trigger 的触发事件,如首次触发、重复触发、结束触发等
- 监控与控制:监听并响应 Job 和 Trigger 的状态变化,如成功、失败、暂停、恢复等;提供 API 用于启动、停止、暂停、恢复整个调度器或单个 Job 与 Trigger
JobBuilder
- 用于构建JobDetail实例
TriggerBuilder
- 用于构建Trigger实例
Quartz中的表
当Quartz被配置为使用JDBC Strore时,它会手动或者自动地在数据库中创建出以qrtz_开头的表,这些表用来存储与调度相关的各种实体和状态信息。
-
qrtz_triggers
- 存储触发器的相关信息。每个触发器对应表中的一条记录
- 主要字段包括:
- trigger_name, trigger_group:组成触发器的唯一标识
- job_name, job_group: 关联的作业名称和组
- trigger_state: 触发器当前的状态;如:WAITING、ACQUIRED、PAUSED、COMPLETE、ERROR等
- trigger_type: 触发器类型;如:SIMPLE、CRON等
- start_time, end_time:触发器的有效起止时间
- next_fire_time, previous_fire_time: 触发器的下一次及上一次实际触发时间
- priority: 触发器的优先级,用于决定多个触发器同时准备就绪时的执行顺序
-
qrtz_job_details
- 存放一个JobDetail的详细信息,每个Job对应表中的一条记录
- 主要字段如下:
- job_class_name: 实现作业逻辑的类的全限定名,Quartz 根据此字段值来实例化作业对象
- job_name 和 job_group:作业的唯一标识,通过名称和组进行区分
- is_durable:标记作业是否持久化。如果为 true,即使调度器关闭,作业也会被保存并在调度器重启后恢复
- is_nonconcurrent:标记作业是否允许并发执行。如果为 true,则同一时刻只能有一个实例运行
- requests_recovery:标记作业是否请求在执行失败后被重新安排执行
-
qrtz_scheduler_state
- 存储调度器实例的状态信息
- 主要字段如下:
- instance_name: 调度器实例的唯一标识
- last_checkin_time: 最近一次心跳检查的时间
- checkin_interval: 心跳检查间隔,用于监控调度器的健康状态
-
qrtz_locks
- 存储 Quartz 内部使用的锁信息,用于协调多节点集群或单节点内的并发访问,保证数据一致性
- 包括各种全局和特定资源的锁记录,如 TRIGGER_ACCESS, JOB_ACCESS, CALENDAR_ACCESS 等
-
qrtz_cron_triggers
- 专门用于存储 CronTrigger 类型触发器的 Cron 表达式及其相关配置
- cron_expression字段是用于定义复杂时间规则的Cron表达式
-
qrtz_blob_triggers
- 用于存储二进制大对象(BLOB)类型的触发器数据,通常用于存储那些无法用标准字段表示的复杂触发器信息
-
qrtz_calendars
- 存储 Quartz 日历(Calendar)对象,用于定义排除某些特定时间范围(如节假日)的复杂调度规则
-
qrtz_simple_triggers
- 仅适用于简单触发器(SimpleTrigger),存储简单触发器特有的重复次数、间隔等信息。
- 如果触发器是基于时间间隔的简单触发器,其详细配置将存储在此表中
-
qrtz_fired_triggers
- 记录已触发(fired)但尚未完成的触发器实例。当触发器触发后,直到作业执行完毕或触发器完成其生命周期,相应的记录才会从这张表中删除
- 用于跟踪调度器内部状态,确保作业正确执行和故障恢复
-
qrtz_job_listeners 和 qrtz_trigger_listeners
- 分别用于存储作业监听器(JobListener)和触发器监听器(TriggerListener)的配置信息,这些监听器可以在作业执行的特定阶段被触发以执行额外操作
Quartz使用
引入Quartz依赖,目前Quartz的最新稳定版本为2.3.0
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.0</version>
</dependency>
Quartz配置
spring:
quartz:
job-store-type: jdbc
jdbc:
initialize-schema: always #每次启动时创建quartz表,第一次启动后请改为never,不然会报错
application:
name: quartz-demo
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/cloud-demo?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
username: root
password: root
using:
spring:
schedulerFactory: true #控制使用哪种整合方式
Quartz单独使用
Quartz配置文件如下
# thread-pool
org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount=10
org.quartz.threadPool.threadPriority=5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread=true
## Spring boot 3.0以后需要使用LocalDataSourceJobStore类型
org.quartz.jobStore.class=org.springframework.scheduling.quartz.LocalDataSourceJobStore
#??????????????????????Triggers???????????????????????????????????60000?60???
org.quartz.jobStore.misfireThreshold=60000
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.dataSource=quartzDataSource
Job实现类:
@Component
@DisallowConcurrentExecution
public class SampleJob implements Job {
private static final Logger logger = LoggerFactory.getLogger(SampleJob.class);
/**
* 当触发器Trigger被触发时会执行execute方法
* @param jobExecutionContext
* @throws JobExecutionException
*/
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
JobDetail jobDetail = jobExecutionContext.getJobDetail();
String description = jobDetail.getDescription();
String name = jobDetail.getKey().getName();
String group = jobDetail.getKey().getGroup();
logger.info("jobDetail:{},name:{},group:{}", description, name, group);
}
}
JobDetail、Trigger、Scheduler配置
@Configuration
@ConditionalOnExpression("'${using.spring.schedulerFactory}' == 'false'")
public class QuartzConfiguration {
private static final Logger LOGGER = LoggerFactory.getLogger(QuartzConfiguration.class);
@Autowired
private ApplicationContext applicationContext;
@PostConstruct
public void init() {
LOGGER.info("hello world from Quartz...");
}
@Bean(name = "springBeanJobFactory")
public SpringBeanJobFactory springBeanJobFactory() {
SpringBeanJobFactory springBeanJobFactory = new SpringBeanJobFactory();
LOGGER.debug("configuring Job factory");
springBeanJobFactory.setApplicationContext(applicationContext);
return springBeanJobFactory;
}
@Bean(name = "scheduler")
public Scheduler scheduler(@Autowired Trigger trigger, @Autowired JobDetail jobDetail,
@Autowired SchedulerFactoryBean factoryBean) throws SchedulerException {
Scheduler scheduler = factoryBean.getScheduler();
scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();
return scheduler;
}
@Bean(name = "schedulerFactoryBean")
public SchedulerFactoryBean schedulerFactoryBean() throws IOException {
SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
schedulerFactoryBean.setJobFactory(springBeanJobFactory());
schedulerFactoryBean.setQuartzProperties(quartzProperties());
return schedulerFactoryBean;
}
public Properties quartzProperties() throws IOException {
PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
propertiesFactoryBean.setLocation(new ClassPathResource("quartz.properties"));
propertiesFactoryBean.setIgnoreResourceNotFound(false);
propertiesFactoryBean.setSingleton(true);
try {
propertiesFactoryBean.afterPropertiesSet();
} catch (Exception e) {
LOGGER.error("quartz properties load fail", e);
}
return propertiesFactoryBean.getObject();
}
@Bean(name = "jobDetail")
public JobDetail jobDetail() {
return JobBuilder.newJob(SampleJob.class).storeDurably().withIdentity(JobKey.jobKey("Quartz_key"))
.withDescription("SampleJob")
.build();
}
@Bean(name = "trigger")
public Trigger trigger(@Autowired JobDetail jobDetail) {
Trigger trigger = TriggerBuilder.newTrigger().forJob(jobDetail)
.withIdentity(TriggerKey.triggerKey("Quartz_Trigger"))
.withDescription("Sample Trigger")
.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(10).repeatForever())
.build();
return trigger;
}
}
Spring Boot整合Quartz
@Configuration
@ConditionalOnExpression("'${using.spring.schedulerFactory}'== 'true'")
public class SpringQuartzConfiguration {
private static final Logger logger = LoggerFactory.getLogger(SpringQuartzConfiguration.class);
@Autowired
private ApplicationContext applicationContext;
@PostConstruct
public void init() {
logger.debug("SpringQuartzConfiguration init");
}
@Bean(name = "springBeanJobFactory")
public SpringBeanJobFactory springBeanJobFactory() {
logger.debug("configure job factory");
AutoWiringSpringBeanJobFactory autoWiringSpringBeanJobFactory = new AutoWiringSpringBeanJobFactory();
autoWiringSpringBeanJobFactory.setApplicationContext(applicationContext);
return autoWiringSpringBeanJobFactory;
}
@Bean
public SchedulerFactoryBean scheduler(@Autowired Trigger trigger, JobDetail jobDetail, DataSource dataSource) {
SchedulerFactoryBean factoryBean = new SchedulerFactoryBean();
//设置quartz配置文件
factoryBean.setConfigLocation(new ClassPathResource("quartz.properties"));
factoryBean.setJobFactory(springBeanJobFactory());
//设置
factoryBean.setJobDetails(jobDetail);
//实则触发器
factoryBean.setTriggers(trigger);
//设置数据源
factoryBean.setDataSource(dataSource);
return factoryBean;
}
@Bean
public JobDetailFactoryBean jobDetail() {
JobDetailFactoryBean jobDetailFactoryBean = new JobDetailFactoryBean();
jobDetailFactoryBean.setJobClass(SampleJob.class);
jobDetailFactoryBean.setDescription("");
jobDetailFactoryBean.setDurability(true);
return jobDetailFactoryBean;
}
@Bean
public SimpleTriggerFactoryBean simpleTriggerFactoryBean(JobDetail jobDetail) {
SimpleTriggerFactoryBean simpleTriggerFactoryBean = new SimpleTriggerFactoryBean();
simpleTriggerFactoryBean.setJobDetail(jobDetail);
int frequencyInSec = 10;
simpleTriggerFactoryBean.setRepeatInterval(frequencyInSec * 1000);
simpleTriggerFactoryBean.setRepeatCount(SimpleTrigger.REPEAT_INDEFINITELY);
simpleTriggerFactoryBean.setName("Quartz_Name");
return simpleTriggerFactoryBean;
}
@Bean
@QuartzDataSource
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
}