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框架简化配置和管理
相关推荐
GottdesKrieges14 小时前
OceanBase恢复常见问题
java·数据库·oceanbase
IGAn CTOU14 小时前
Java高级开发进阶教程之系列
java·开发语言
leo825...14 小时前
Claude Code Skills 清单(本地)
java·python·ai编程
NGSI vimp15 小时前
Java进阶——如何查看Java字节码
java·开发语言
身如柳絮随风扬16 小时前
多数据源切换实战:从业务场景到3种实现方案全解析
java·分布式·微服务
Java小生不才16 小时前
Spring AI文生音
java·人工智能·spring
凯尔萨厮16 小时前
Springboot2.x+Thymeleaf项目创建
java
fish_xk16 小时前
map和set
java·开发语言
李崧正17 小时前
Java技术分享:Lambda表达式与函数式编程
java·开发语言·python
老了,不知天命17 小时前
鳶尾花項目JAVA
java·开发语言·机器学习