Java的Quartz定时任务引擎详解

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 是一个功能完整的任务调度框架,通过合理配置可以满足各种复杂的调度需求。在实际使用中,建议:

  1. 根据业务需求选择合适的Trigger类型
  2. 在集群环境中使用持久化存储
  3. 合理处理任务异常和错过执行情况
  4. 使用监听器进行监控和日志记录
  5. 结合Spring框架简化配置和管理
相关推荐
无限大619 分钟前
计算机十万个为什么--数据库索引
后端
冬夜戏雪27 分钟前
【java学习日记】【2025.12.7】【7/60】
java·开发语言·学习
CC.GG36 分钟前
【C++】二叉搜索树
java·c++·redis
学历真的很重要40 分钟前
VsCode+Roo Code+Gemini 2.5 Pro+Gemini Balance AI辅助编程环境搭建(理论上通过多个Api Key负载均衡达到无限免费Gemini 2.5 Pro)
前端·人工智能·vscode·后端·语言模型·负载均衡·ai编程
JIngJaneIL2 小时前
基于Java非遗传承文化管理系统(源码+数据库+文档)
java·开发语言·数据库·vue.js·spring boot
+VX:Fegn08952 小时前
计算机毕业设计|基于springboot + vue心理健康管理系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
南部余额2 小时前
踩坑与解惑:深入理解 SpringBoot 自动配置原理与配置排除机制
java·spring boot·自动配置原理·import
狂炫冰美式3 小时前
不谈技术,搞点文化 🧀 —— 从复活一句明代残诗破局产品迭代
前端·人工智能·后端
木鹅.4 小时前
LangChain4j
java
永远都不秃头的程序员(互关)4 小时前
Java核心技术精要:高效实践指南
java·开发语言·性能优化