Java的Quartz定时任务引擎详解
Quartz 在正确配置集群的情况下,不会出现 Spring Task 那样的重复执行问题。 核心区别在于:
- Quartz:通过数据库锁机制保证集群中只有一个实例执行任务
- Spring Task:每个实例独立调度,需要额外处理分布式协调
因此,在微服务架构和集群部署场景下,Quartz 是更可靠的定时任务解决方案。
1. Quartz 概述
Quartz 是一个功能强大、开源的任务调度框架,可以用来创建简单或复杂的定时任务调度方案。
主要特性
- 灵活的任务调度配置
- 持久化支持(内存、JDBC)
- 集群支持
- 事务支持
- 插件机制
2. 核心组件
2.1 Scheduler
调度器,任务调度的核心控制器
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
2.2 Job
任务接口,定义要执行的工作
public class MyJob implements Job {
@Override
public void execute(JobExecutionContext context) {
// 任务执行逻辑
}
}
2.3 Trigger
触发器,定义任务执行的时间规则
- SimpleTrigger:简单触发器
- CronTrigger:基于Cron表达式的触发器
2.4 JobDetail
任务详情,包含任务的详细信息
3. 基本使用
3.1 添加依赖
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.2</version>
</dependency>
3.2 简单示例
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 实现
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
@PersistJobDataAfterExecution
@DisallowConcurrentExecution
public class StatefulJob implements Job {
@Override
public void execute(JobExecutionContext context) {
// 这个Job不会并发执行,且会持久化JobDataMap
}
}
5. Trigger 详解
5.1 SimpleTrigger
// 立即开始,每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
// 使用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 数据传递
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
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
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 注册监听器
scheduler.getListenerManager().addJobListener(new CustomJobListener());
scheduler.getListenerManager().addTriggerListener(new CustomTriggerListener());
8. 持久化配置
8.1 quartz.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 配置
<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 集成
@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 异常处理
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 性能优化
// 使用@DisallowConcurrentExecution避免并发
@DisallowConcurrentExecution
public class ExpensiveJob implements Job {
// 耗时任务,避免并发执行
}
// 使用@PersistJobDataAfterExecution持久化状态
@PersistJobDataAfterExecution
public class StatefulDataJob implements Job {
// 需要保持状态的Job
}
11. 常见问题
11.1 任务错过执行(Misfire)
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框架简化配置和管理



还没有评论呢,快来抢沙发~