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框架简化配置和管理
相关推荐
xyy1233 小时前
.NET Serilog
后端
Seven973 小时前
SpringCloud 常见面试题(一)
java
kong79069283 小时前
SpringCache缓存
java·spring·缓存
程序猿小蒜3 小时前
基于springboot的汽车资讯网站开发与实现
java·前端·spring boot·后端·spring
それども3 小时前
SpringBoot 切面AOP获取注解为null
java·spring boot·spring
q***98523 小时前
前端的dist包放到后端springboot项目下一起打包
前端·spring boot·后端
vx_bisheyuange3 小时前
基于SpringBoot的热门旅游推荐系统设计与实现
java·spring boot·后端·毕业设计
代码不停3 小时前
Java分治算法题目练习(快速/归并排序)
java·数据结构·算法
代码or搬砖3 小时前
SpringBoot整合SpringMVC
java·spring boot·后端