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框架简化配置和管理
相关推荐
㳺三才人子5 小时前
初探 Flask
后端·python·flask·html
星栈独行5 小时前
我在 Rust 全栈项目里用 JWT 做无状态认证
开发语言·后端·rust·前端框架·开源·github·web
Lei活在当下5 小时前
先用起来,再理解,关于协程Coroutine应该知道的事
android·java·jvm
Java爱好狂.5 小时前
Java程序员体系化学习路线(2026最新版)
java·后端·java面试·java架构师·java程序员·java八股文·java学习路线
陈随易6 小时前
Redis 8.8发布,一定要更新
前端·后端·程序员
tongluowan0076 小时前
以ReentrantLock为例解释AQS的工作流程
java·模板方法模式·aqs·reentrantlock
装不满的克莱因瓶6 小时前
SpringBoot 如何将 lib 目录中jar包打包进最终的jar包里面
spring boot·后端·maven·jar·mvn
ltl7 小时前
Transformer 原论文实验结果:为什么 28.4 BLEU 足以改写路线图
后端
身如柳絮随风扬7 小时前
Java 项目打包与部署完全指南:JAR vs WAR,从构建到运行
java·firefox·jar
云烟成雨TD7 小时前
Spring AI Alibaba 1.x 系列【62】时光旅行(Time-Travel)
java·人工智能·spring