定时任务之Quartz使用

本文详细介绍了Quartz框架的核心概念,如JobDetail、Job、Trigger和Scheduler,以及如何在SpringBoot中集成和配置Quartz,包括使用JDBCStore存储数据和处理并发执行策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

定时任务之Quartz使用

Quartz是一个用Java开发的开源定时任务框架。
Quartz的核心对象如下:

Quartz中的概念介绍

JobDetail

  • JobDetail就是作业详细信息,包含作业名称、作业组名称、作业描述、具体作业实现类型等信息
  • JobDetail有一个唯一的名称(name)和组名(group),它们共同构成了作业在调度器中的唯一标识,通过这种方式,用户可以对不同的作业进行组织和分类
  • JobDetail必须关联一个实现了Job接口的类,该类封装了作业的实际执行逻辑,通常使用JobBuilder的ofClass方法来指定关联的Job
  • JobDetail中有一个JobDataMap属性,使用键值对存储数据,这些数据会在作业执行时传递给关联的Job实例,可用传递运行时上下文所需要的数据

在这里插入图片描述

Job

  • 任务的执行逻辑,任何需要在Quartz中执行的任务都需要实现Job接口
  • Job接口中只有一个方法:void execute(JobExecutionContext context) throws JobExecutionException
  • execute 方法是每个Job 类必须实现的核心逻辑,当作业被触发并分配给工作线程执行时,该方法会被调用
  • JobExecutionContext 参数提供了与作业执行相关的上下文信息,包括触发作业的 Trigger、作业自身的 JobDetail(含 JobDataMap)、调度器的Scheduler等
  • 如果作业的执行时间很长,但是每个作业被触发的间隔时间很短,有可能会出现并发执行的情况;可以通过注解设置并发策略
    • @DisallowConcurrentExecution:标记在 Job 类上,指示调度器不应在同一时刻并发执行同一个作业实例
    • @PersistJobDataAfterExecution:标记在 Job 类上,表示作业成功执行后,应将修改过的 JobDataMap 数据持久化保存

Trigger

  • 触发器,定义Job的触发时机、重复频率以及其它的调度相关的细节;Trigger 必须与一个已定义的 JobDetail 关联,表明它将触发哪个作业的执行

  • SimpleTrigger:用于安排一次性或重复一定次数的简单调度任务;SimpleTrigger的主要属性如下

    • startTime:触发器首次生效的时间点
    • endTime:触发器失效的时间点(可选),超过此时间则不再触发作业
    • repeatCount: 重复执行的次数(SimpleTrigger),0 表示只执行一次,-1 或 RepeatIndefinitely 表示无限次重复
    • repeatInterval: 重复执行之间的间隔时间(单位通常是毫秒,SimpleTrigger)
  • CronTrigger: 基于 Cron 表达式的复杂调度触发器,支持按照诸如分钟、小时、天、月、周等周期性模式来触发作业。CronTrigger 提供了极大的灵活性,适用于各种复杂的定时任务场景,如每周一至周五的某个时段执行、每月最后一个工作日执行等

  • Trigger 有其独立的生命周期,可以被暂停、恢复、删除,也可以动态修改其触发规则

  • Misfire Instructions:

    • 当调度器因故未能在预期时间触发作业时(称为 Misfire),可以通过设置 Misfire Instructions 指定在这种情况下应该如何处理。例如,可以选择立即执行、跳过此次执行、按原计划下一次触发时间执行等

Scheduler

  • Scheduler是Quartz框架的核心组件,它负责管理和调度所有Job及其对应的触发器,主要职责包括:
    • 注册和管理Job:Scheduler 维护一个内部的 Job 注册表,用于存储所有已定义的 JobDetail 对象。提供 API 用于添加、修改、查询和删除 JobDetail
    • 调度 Triggers:根据关联的 Trigger 规则,决定何时触发相应的 Job 执行。处理 Trigger 的触发事件,如首次触发、重复触发、结束触发等
    • 监控与控制:监听并响应 Job 和 Trigger 的状态变化,如成功、失败、暂停、恢复等;提供 API 用于启动、停止、暂停、恢复整个调度器或单个 Job 与 Trigger
      JobBuilder
  • 用于构建JobDetail实例

TriggerBuilder

  • 用于构建Trigger实例

Quartz中的表

当Quartz被配置为使用JDBC Strore时,它会手动或者自动地在数据库中创建出以qrtz_开头的表,这些表用来存储与调度相关的各种实体和状态信息。

  • qrtz_triggers

    • 存储触发器的相关信息。每个触发器对应表中的一条记录
    • 主要字段包括:
      • trigger_name, trigger_group:组成触发器的唯一标识
      • job_name, job_group: 关联的作业名称和组
      • trigger_state: 触发器当前的状态;如:WAITING、ACQUIRED、PAUSED、COMPLETE、ERROR等
      • trigger_type: 触发器类型;如:SIMPLE、CRON等
      • start_time, end_time:触发器的有效起止时间
      • next_fire_time, previous_fire_time: 触发器的下一次及上一次实际触发时间
      • priority: 触发器的优先级,用于决定多个触发器同时准备就绪时的执行顺序
  • qrtz_job_details

    • 存放一个JobDetail的详细信息,每个Job对应表中的一条记录
    • 主要字段如下:
      • job_class_name: 实现作业逻辑的类的全限定名,Quartz 根据此字段值来实例化作业对象
    • job_name 和 job_group:作业的唯一标识,通过名称和组进行区分
    • is_durable:标记作业是否持久化。如果为 true,即使调度器关闭,作业也会被保存并在调度器重启后恢复
    • is_nonconcurrent:标记作业是否允许并发执行。如果为 true,则同一时刻只能有一个实例运行
    • requests_recovery:标记作业是否请求在执行失败后被重新安排执行
  • qrtz_scheduler_state

    • 存储调度器实例的状态信息
    • 主要字段如下:
      • instance_name: 调度器实例的唯一标识
      • last_checkin_time: 最近一次心跳检查的时间
      • checkin_interval: 心跳检查间隔,用于监控调度器的健康状态
  • qrtz_locks

    • 存储 Quartz 内部使用的锁信息,用于协调多节点集群或单节点内的并发访问,保证数据一致性
    • 包括各种全局和特定资源的锁记录,如 TRIGGER_ACCESS, JOB_ACCESS, CALENDAR_ACCESS 等
  • qrtz_cron_triggers

    • 专门用于存储 CronTrigger 类型触发器的 Cron 表达式及其相关配置
    • cron_expression字段是用于定义复杂时间规则的Cron表达式
  • qrtz_blob_triggers

    • 用于存储二进制大对象(BLOB)类型的触发器数据,通常用于存储那些无法用标准字段表示的复杂触发器信息
  • qrtz_calendars

    • 存储 Quartz 日历(Calendar)对象,用于定义排除某些特定时间范围(如节假日)的复杂调度规则
  • qrtz_simple_triggers

    • 仅适用于简单触发器(SimpleTrigger),存储简单触发器特有的重复次数、间隔等信息。
    • 如果触发器是基于时间间隔的简单触发器,其详细配置将存储在此表中
  • qrtz_fired_triggers

    • 记录已触发(fired)但尚未完成的触发器实例。当触发器触发后,直到作业执行完毕或触发器完成其生命周期,相应的记录才会从这张表中删除
    • 用于跟踪调度器内部状态,确保作业正确执行和故障恢复
  • qrtz_job_listeners 和 qrtz_trigger_listeners

    • 分别用于存储作业监听器(JobListener)和触发器监听器(TriggerListener)的配置信息,这些监听器可以在作业执行的特定阶段被触发以执行额外操作

Quartz使用

引入Quartz依赖,目前Quartz的最新稳定版本为2.3.0

        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
            <version>2.3.0</version>
        </dependency>

Quartz配置

spring:
  quartz:
    job-store-type: jdbc
    jdbc:
      initialize-schema: always  #每次启动时创建quartz表,第一次启动后请改为never,不然会报错
  application:
    name: quartz-demo
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    jdbc-url: jdbc:mysql://localhost:3306/cloud-demo?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
    username: root
    password: root

using:
  spring:
    schedulerFactory: true  #控制使用哪种整合方式

Quartz单独使用

Quartz配置文件如下

# thread-pool
org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount=10
org.quartz.threadPool.threadPriority=5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread=true
 
## Spring boot 3.0以后需要使用LocalDataSourceJobStore类型
org.quartz.jobStore.class=org.springframework.scheduling.quartz.LocalDataSourceJobStore
#??????????????????????Triggers???????????????????????????????????60000?60???
org.quartz.jobStore.misfireThreshold=60000
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.dataSource=quartzDataSource

Job实现类:

@Component
@DisallowConcurrentExecution
public class SampleJob implements Job {

    private static final Logger logger = LoggerFactory.getLogger(SampleJob.class);

    /**
     * 当触发器Trigger被触发时会执行execute方法
     * @param jobExecutionContext
     * @throws JobExecutionException
     */
    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        JobDetail jobDetail = jobExecutionContext.getJobDetail();
        String description = jobDetail.getDescription();
        String name = jobDetail.getKey().getName();
        String group = jobDetail.getKey().getGroup();
        logger.info("jobDetail:{},name:{},group:{}", description, name, group);
    }

}

JobDetail、Trigger、Scheduler配置

@Configuration
@ConditionalOnExpression("'${using.spring.schedulerFactory}' == 'false'")
public class QuartzConfiguration {

    private static final Logger LOGGER = LoggerFactory.getLogger(QuartzConfiguration.class);

    @Autowired
    private ApplicationContext applicationContext;

    @PostConstruct
    public void init() {
        LOGGER.info("hello world from Quartz...");
    }

    @Bean(name = "springBeanJobFactory")
    public SpringBeanJobFactory springBeanJobFactory() {
        SpringBeanJobFactory  springBeanJobFactory = new SpringBeanJobFactory();
        LOGGER.debug("configuring Job factory");
        springBeanJobFactory.setApplicationContext(applicationContext);
        return springBeanJobFactory;
    }

    @Bean(name = "scheduler")
    public Scheduler scheduler(@Autowired Trigger trigger, @Autowired JobDetail jobDetail,
                               @Autowired SchedulerFactoryBean factoryBean) throws SchedulerException {
        Scheduler scheduler = factoryBean.getScheduler();
        scheduler.scheduleJob(jobDetail, trigger);
        scheduler.start();
        return scheduler;
    }


    @Bean(name = "schedulerFactoryBean")
    public SchedulerFactoryBean schedulerFactoryBean() throws IOException {
        SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
        schedulerFactoryBean.setJobFactory(springBeanJobFactory());
        schedulerFactoryBean.setQuartzProperties(quartzProperties());
        return schedulerFactoryBean;
    }



    public Properties quartzProperties() throws IOException {
        PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
        propertiesFactoryBean.setLocation(new ClassPathResource("quartz.properties"));
        propertiesFactoryBean.setIgnoreResourceNotFound(false);
        propertiesFactoryBean.setSingleton(true);
        try {
            propertiesFactoryBean.afterPropertiesSet();
        } catch (Exception e) {
            LOGGER.error("quartz properties load fail", e);
        }
        return propertiesFactoryBean.getObject();
    }


    @Bean(name = "jobDetail")
    public JobDetail jobDetail() {
        return JobBuilder.newJob(SampleJob.class).storeDurably().withIdentity(JobKey.jobKey("Quartz_key"))
                .withDescription("SampleJob")
                .build();
    }


    @Bean(name = "trigger")
    public Trigger trigger(@Autowired JobDetail jobDetail) {
        Trigger trigger = TriggerBuilder.newTrigger().forJob(jobDetail)
                .withIdentity(TriggerKey.triggerKey("Quartz_Trigger"))
                .withDescription("Sample Trigger")
                .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(10).repeatForever())
                .build();
        return trigger;
    }

}

Spring Boot整合Quartz

@Configuration
@ConditionalOnExpression("'${using.spring.schedulerFactory}'== 'true'")
public class SpringQuartzConfiguration {

    private static final Logger logger = LoggerFactory.getLogger(SpringQuartzConfiguration.class);

    @Autowired
    private ApplicationContext applicationContext;

    @PostConstruct
    public void init() {
        logger.debug("SpringQuartzConfiguration init");
    }


    @Bean(name = "springBeanJobFactory")
    public SpringBeanJobFactory springBeanJobFactory() {
        logger.debug("configure job factory");
        AutoWiringSpringBeanJobFactory autoWiringSpringBeanJobFactory = new AutoWiringSpringBeanJobFactory();
        autoWiringSpringBeanJobFactory.setApplicationContext(applicationContext);
        return autoWiringSpringBeanJobFactory;
    }


    @Bean
    public SchedulerFactoryBean scheduler(@Autowired Trigger trigger, JobDetail jobDetail, DataSource dataSource) {
        SchedulerFactoryBean factoryBean = new SchedulerFactoryBean();
        //设置quartz配置文件
        factoryBean.setConfigLocation(new ClassPathResource("quartz.properties"));
        factoryBean.setJobFactory(springBeanJobFactory());
        //设置
        factoryBean.setJobDetails(jobDetail);
        //实则触发器
        factoryBean.setTriggers(trigger);
        //设置数据源
        factoryBean.setDataSource(dataSource);
        return factoryBean;
    }



    @Bean
    public JobDetailFactoryBean jobDetail() {
        JobDetailFactoryBean jobDetailFactoryBean = new JobDetailFactoryBean();
        jobDetailFactoryBean.setJobClass(SampleJob.class);
        jobDetailFactoryBean.setDescription("");

        jobDetailFactoryBean.setDurability(true);
        return jobDetailFactoryBean;
    }


    @Bean
    public SimpleTriggerFactoryBean simpleTriggerFactoryBean(JobDetail jobDetail) {
        SimpleTriggerFactoryBean simpleTriggerFactoryBean = new SimpleTriggerFactoryBean();
        simpleTriggerFactoryBean.setJobDetail(jobDetail);
        int frequencyInSec = 10;
        simpleTriggerFactoryBean.setRepeatInterval(frequencyInSec * 1000);
        simpleTriggerFactoryBean.setRepeatCount(SimpleTrigger.REPEAT_INDEFINITELY);
        simpleTriggerFactoryBean.setName("Quartz_Name");
        return simpleTriggerFactoryBean;
    }


    @Bean
    @QuartzDataSource
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource dataSource() {
        return DataSourceBuilder.create().build();
    }
    
}

参考

  1. Scheduling in Spring with Quartz
  2. spring-quartz代码示例
  3. SpringBoot 之 Quartz 使用
  4. 分布式定时任务框架选型,写得太好了!
  5. 浅析后端微服务涉及到定时任务时如何解决多集群定时任务重复执行并发的方案对比
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值