Java的Quartz定时任务引擎详解
Quartz 在正确配置集群的情况下,不会出现 Spring Task 那样的重复执行问题。 核心区别在于:
- Quartz:通过数据库锁机制保证集群中只有一个实例执行任务
- Spring Task:每个实例独立调度,需要额外处理分布式协调
因此,在微服务架构和集群部署场景下,Quartz 是更可靠的定时任务解决方案。
1. Quartz 概述
Quartz 是一个功能强大、开源的任务调度框架,可以用来创建简单或复杂的定时任务调度方案。
主要特性
- 灵活的任务调度配置
- 持久化支持(内存、JDBC)
- 集群支持
- 事务支持
- 插件机制
2. 核心组件
2.1 Scheduler
调度器,任务调度的核心控制器
java
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
2.2 Job
任务接口,定义要执行的工作
java
public class MyJob implements Job {
@Override
public void execute(JobExecutionContext context) {
// 任务执行逻辑
}
}
2.3 Trigger
触发器,定义任务执行的时间规则
- SimpleTrigger:简单触发器
- CronTrigger:基于Cron表达式的触发器
2.4 JobDetail
任务详情,包含任务的详细信息
3. 基本使用
3.1 添加依赖
xml
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.2</version>
</dependency>
3.2 简单示例
java
public class QuartzDemo {
public static void main(String[] args) throws SchedulerException {
// 1. 创建调度器
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
// 2. 启动调度器
scheduler.start();
// 3. 创建任务
JobDetail job = JobBuilder.newJob(MyJob.class)
.withIdentity("myJob", "group1")
.build();
// 4. 创建触发器
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("myTrigger", "group1")
.startNow()
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(10)
.repeatForever())
.build();
// 5. 将任务和触发器注册到调度器
scheduler.scheduleJob(job, trigger);
}
}
4. Job 详解
4.1 Job 实现
java
public class DataProcessJob implements Job {
@Override
public void execute(JobExecutionContext context) {
// 获取JobDetail中的数据
JobDataMap dataMap = context.getJobDetail().getJobDataMap();
String jobName = dataMap.getString("jobName");
// 获取Trigger中的数据
JobDataMap triggerDataMap = context.getTrigger().getJobDataMap();
String triggerName = triggerDataMap.getString("triggerName");
// 执行任务逻辑
System.out.println("执行任务: " + jobName);
}
}
4.2 有状态Job
java
@PersistJobDataAfterExecution
@DisallowConcurrentExecution
public class StatefulJob implements Job {
@Override
public void execute(JobExecutionContext context) {
// 这个Job不会并发执行,且会持久化JobDataMap
}
}
5. Trigger 详解
5.1 SimpleTrigger
java
// 立即开始,每10秒执行一次,总共执行5次
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger1", "group1")
.startNow()
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(10)
.withRepeatCount(4)) // 重复4次,总共5次
.build();
// 指定开始时间,每天执行
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger2", "group1")
.startAt(DateBuilder.todayAt(10, 0, 0)) // 今天10:00开始
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInHours(24)
.repeatForever())
.build();
5.2 CronTrigger
java
// 使用Cron表达式
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger3", "group1")
.withSchedule(CronScheduleBuilder.cronSchedule("0 0 12 * * ?")) // 每天12:00执行
.build();
// 常用的Cron表达式示例
// "0 0/5 * * * ?" 每5分钟执行一次
// "0 0 9-17 * * ?" 朝九晚五内每小时执行一次
// "0 0 12 ? * WED" 每周三12:00执行
// "0 15 10 ? * MON-FRI" 周一到周五10:15执行
6. JobDataMap 数据传递
java
public class DataProcessJob implements Job {
@Override
public void execute(JobExecutionContext context) {
// 获取JobDataMap中的数据
JobDataMap dataMap = context.getMergedJobDataMap();
String dataSource = dataMap.getString("dataSource");
int batchSize = dataMap.getInt("batchSize");
processData(dataSource, batchSize);
}
private void processData(String dataSource, int batchSize) {
// 数据处理逻辑
}
}
// 创建Job时设置数据
JobDetail job = JobBuilder.newJob(DataProcessJob.class)
.withIdentity("dataJob", "group1")
.usingJobData("dataSource", "mysql")
.usingJobData("batchSize", 1000)
.build();
// 创建Trigger时也可以设置数据
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("dataTrigger", "group1")
.usingJobData("triggerName", "dailyTrigger")
.withSchedule(CronScheduleBuilder.dailyAtHourAndMinute(14, 30))
.build();
7. 监听器
7.1 JobListener
java
public class CustomJobListener implements JobListener {
@Override
public String getName() {
return "CustomJobListener";
}
@Override
public void jobToBeExecuted(JobExecutionContext context) {
System.out.println("Job即将执行: " + context.getJobDetail().getKey());
}
@Override
public void jobExecutionVetoed(JobExecutionContext context) {
System.out.println("Job执行被否决: " + context.getJobDetail().getKey());
}
@Override
public void jobWasExecuted(JobExecutionContext context,
JobExecutionException jobException) {
System.out.println("Job执行完成: " + context.getJobDetail().getKey());
}
}
7.2 TriggerListener
java
public class CustomTriggerListener implements TriggerListener {
@Override
public String getName() {
return "CustomTriggerListener";
}
@Override
public void triggerFired(Trigger trigger, JobExecutionContext context) {
System.out.println("Trigger触发: " + trigger.getKey());
}
@Override
public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) {
// 返回true表示否决Job执行
return false;
}
@Override
public void triggerMisfired(Trigger trigger) {
System.out.println("Trigger错过触发: " + trigger.getKey());
}
@Override
public void triggerComplete(Trigger trigger, JobExecutionContext context,
Trigger.CompletedExecutionInstruction triggerInstructionCode) {
System.out.println("Trigger执行完成: " + trigger.getKey());
}
}
7.3 注册监听器
java
scheduler.getListenerManager().addJobListener(new CustomJobListener());
scheduler.getListenerManager().addTriggerListener(new CustomTriggerListener());
8. 持久化配置
8.1 quartz.properties
properties
# 配置数据源
org.quartz.dataSource.myDS.driver = com.mysql.cj.jdbc.Driver
org.quartz.dataSource.myDS.URL = jdbc:mysql://localhost:3306/quartz
org.quartz.dataSource.myDS.user = root
org.quartz.dataSource.myDS.password = password
org.quartz.dataSource.myDS.maxConnections = 10
# 使用JDBC JobStore
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.tablePrefix = QRTZ_
org.quartz.jobStore.dataSource = myDS
# 集群配置
org.quartz.jobStore.isClustered = true
org.quartz.jobStore.clusterCheckinInterval = 20000
9. Spring 集成
9.1 Spring 配置
xml
<bean id="schedulerFactoryBean"
class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="simpleTrigger"/>
<ref bean="cronTrigger"/>
</list>
</property>
<property name="jobDetails">
<list>
<ref bean="jobDetail"/>
</list>
</property>
</bean>
<bean id="jobDetail"
class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<property name="jobClass" value="com.example.MyJob"/>
<property name="jobDataAsMap">
<map>
<entry key="timeout" value="5"/>
</map>
</property>
</bean>
<bean id="simpleTrigger"
class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
<property name="jobDetail" ref="jobDetail"/>
<property name="repeatInterval" value="10000"/>
<property name="startDelay" value="1000"/>
</bean>
9.2 Spring Boot 集成
java
@Configuration
public class QuartzConfig {
@Bean
public JobDetail sampleJobDetail() {
return JobBuilder.newJob(SampleJob.class)
.withIdentity("sampleJob")
.storeDurably()
.build();
}
@Bean
public Trigger sampleJobTrigger() {
SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(10)
.repeatForever();
return TriggerBuilder.newTrigger()
.forJob(sampleJobDetail())
.withIdentity("sampleTrigger")
.withSchedule(scheduleBuilder)
.build();
}
}
10. 最佳实践
10.1 异常处理
java
public class RobustJob implements Job {
@Override
public void execute(JobExecutionContext context) {
try {
// 业务逻辑
doBusiness();
} catch (Exception e) {
// 记录异常但不抛出,避免任务被停止
log.error("Job执行异常", e);
// 如果需要重试,可以重新调度
if (needRetry()) {
rescheduleJob(context);
}
}
}
private void rescheduleJob(JobExecutionContext context) {
try {
Scheduler scheduler = context.getScheduler();
Trigger oldTrigger = context.getTrigger();
Trigger newTrigger = oldTrigger.getTriggerBuilder()
.startAt(new Date(System.currentTimeMillis() + 60000)) // 1分钟后重试
.build();
scheduler.rescheduleJob(oldTrigger.getKey(), newTrigger);
} catch (SchedulerException e) {
log.error("重新调度任务失败", e);
}
}
}
10.2 性能优化
java
// 使用@DisallowConcurrentExecution避免并发
@DisallowConcurrentExecution
public class ExpensiveJob implements Job {
// 耗时任务,避免并发执行
}
// 使用@PersistJobDataAfterExecution持久化状态
@PersistJobDataAfterExecution
public class StatefulDataJob implements Job {
// 需要保持状态的Job
}
11. 常见问题
11.1 任务错过执行(Misfire)
java
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger")
.withSchedule(CronScheduleBuilder.cronSchedule("0 0/5 * * * ?")
.withMisfireHandlingInstructionFireAndProceed()) // 错过处理策略
.build();
// 常用的错过处理策略:
// withMisfireHandlingInstructionIgnoreMisfires() - 忽略并立即执行
// withMisfireHandlingInstructionFireAndProceed() - 立即执行一次,然后按计划
// withMisfireHandlingInstructionDoNothing() - 什么都不做
11.2 集群环境
在集群环境中,确保:
- 所有节点的时钟同步
- 使用JDBC JobStore
- 配置合适的clusterCheckinInterval
- 避免在Job中保存状态,使用数据库共享状态
总结
Quartz 是一个功能完整的任务调度框架,通过合理配置可以满足各种复杂的调度需求。在实际使用中,建议:
- 根据业务需求选择合适的Trigger类型
- 在集群环境中使用持久化存储
- 合理处理任务异常和错过执行情况
- 使用监听器进行监控和日志记录
- 结合Spring框架简化配置和管理