首页 开发教程 Java的Quartz定时任务引擎详解

Java的Quartz定时任务引擎详解

开发教程 2025年12月4日
857 浏览

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

  1. 根据业务需求选择合适的Trigger类型
  2. 在集群环境中使用持久化存储
  3. 合理处理任务异常和错过执行情况
  4. 使用监听器进行监控和日志记录
  5. 结合Spring框架简化配置和管理

发表评论
暂无评论

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

客服

点击联系客服 点击联系客服

在线时间:09:00-18:00

关注微信公众号

关注微信公众号
客服电话

400-888-8888

客服邮箱 122325244@qq.com

手机

扫描二维码

手机访问本站

扫描二维码
搜索