Spring Quartz分布式定时任务框架搭建

代码版本

使用的版本是2.3.2,无漏洞,是最新的发行版本。

参考

Quartz官网

2.3.2源码github

官方指导翻译参考 --注意该指导不是最新的版本

基于spring+quartz的分布式定时任务框架

建表

建表脚本在源码包的位置

由于使用的是分布式方式,需要建表。使用MySql的InnoDb的引擎建表。脚本在源码包的。

quartz-2.3.2\quartz-core\src\main\resources\org\quartz\impl\jdbcjobstore

Quartz11张表的解释

参考

参考2

序号 表名 说明 备注
1 QRTZ_JOB_DETAILS 存放JobDetail的具体信息。核心:JobName,JobGroup,描述,ClassName与是否独立存储等
2 QRTZ_TRIGGERS 存储所有定义的Trigger信息,包括名称、分组、类型(CRON或SIMPLE)、当前运行状态、上下次执行时间、MisFire策略。和qrtz_fired_triggers存放的不一样,不管trigger触发了多少次都只有一条记录,TRIGGER_STATE用来标识当前trigger的状态
3 QRTZ_SIMPLE_TRIGGERS 存储SIMPLE类型的Trigger(SimpleTrigger),存储名称、分组、重复次数、执行时间间隔已经已经执行的次数。失火策略该表不存,存放于ORTZ_TRIGGER表中。 不使用SimpleTrigger,不需要关注
4 QRTZ_CRON_TRIGGERS 存储CRON类型的Trigger(CronTrigger),最常用的触发器。包括名称、所属组、Cron表达式、时区信息 重点,需要使用CronTrigger
5 QRTZ_SIMPROP_TRIGGERS 存储CalendarIntervalTrigger和DailyTimeIntervalTrigger两种类型的触发器 不使用,可暂不关注
6 QRTZ_BLOB_TRIGGERS 自定义的triggers使用blog类型进行存储,非自定义的triggers不会存放在此表中,Quartz提供的triggers包括:CronTrigger,CalendarIntervalTrigger, DailyTimeIntervalTrigger以及SimpleTrigger,这几个trigger信息会保存在其他的几张表中 不需要自定义trigger,可暂不关注
7 QRTZ_CALENDARS Quartz为我们提供了日历的功能,可以自己定义一个时间段,可以控制触发器在这个时间段内触发或者不触发;现在提供6种类型:AnnualCalendar,CronCalendar,DailyCalendar,HolidayCalendar,MonthlyCalendar,WeeklyCalendar 暂不使用,使用cron表达式目前已经满足需要
8 QRTZ_PAUSED_TRIGGER_GRPS 存储暂停的Trigger组 重要性低,可暂不关注
9 QRTZ_FIRED_TRIGGERS 存储正在执行任务中的Trgigger,trigger随着时间的推移状态发生变化,直到最后trigger执行完成,从表中被删除。主要存储运行服务器节点ID、Trigger名称分组、触发器执行状态、触发与调度时间。相同的trigger和task,每触发一次都会创建一个实例;从刚被创建的ACQUIRED状态,到EXECUTING状态,最后执行完从数据库中删除;
10 QRTZ_SCHEDULER_STATE 存储所有节点的scheduler,会定期检查scheduler是否失效。上次检查时间与检测状态。
11 QRTZ_LOCKS Quartz提供的锁表,为多个节点调度提供分布式锁,实现分布式调度

脚手架搭建

数据库初始化

在数据库环境上新建数据库common,执行quartz的初始化脚本tables_mysql_innodb.sql

使用Spring提供的Quartz支持

<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.2</version>
</dependency>
<dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-context-support</artifactId>
   <version>5.2.5.RELEASE</version>
 </dependency>

核心类

参考如下,可以整改为基于注解的方式

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:task="http://www.springframework.org/schema/task"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
        http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd">

    <bean name="secondCron" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
        <property name="jobClass" value="com.hafiz.www.cron.SecondCron"/>
        <property name="jobDataAsMap">
            <map>
                <entry key="timeout" value="0"/>
            </map>
        </property>
    </bean>

    <!--Cron表达式触发器-->
    <bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
        <property name="jobDetail" ref="secondCron"/>
        <property name="cronExpression" value="0/5 * * * * ?"/>
    </bean>

    <!--配置调度工厂-->
    <bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
        <property name="triggers">
            <list>
                <!--<ref bean="simpleTrigger"/>-->
                <ref bean="cronTrigger"/>
            </list>
        </property>
    </bean>
</beans>

基本关系是,SchedulerFactoryBean通过触发器JobDetailFactoryBean执行包含任务类SecondCron的JobDetailFactoryBean。其中SecondCron是业务实现。

Spring与Quartz的整合

参考下面的链接初始化表信息及配置。

参考

可以将集成修改为注入形式,注入方式配置参考下面的链接:

参考

Spring 整合 Quartz 分布式调度

参考

job中无法注入service解决

job实例在spring容器加载时候,能够注入bean,但是调度时,job对象会重新创建,此时就是导致已经注入的对象丢失,因此报空指针异常。通过重写JobFactory解决该问题

代码

1、配置文件database.properties、quartz.properties、cron.properties

通过database.properties初始化DataSource数据源(可以是Druid等任意实现了DataSource的框架)

@Configuration
@PropertySource("classpath:database.properties")
public class DataSourceConfiguration {
    @Value("${servers}")
    private String servers;

    @Value("${userName}")
    private String userName;

    @Value("${encPasswd}")
    private String encPasswd;

    @Bean
    public DataSource dataSource() {

        DataSource dataSource = new DataSource();
        dataSource.setServers(servers);
        dataSource.setUserName(userName);
        dataSource.setEncPasswd(encPasswd);
        
        return dataSource;
    }
}

quartz.properties

#============================================================================
# Configure Main Scheduler Properties
#============================================================================

org.quartz.scheduler.instanceName = petal-quartz
org.quartz.scheduler.instanceId = AUTO
org.quartz.scheduler.threadsInheritContextClassLoaderOfInitializer = true

#============================================================================
# Configure ThreadPool
#============================================================================

org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = 50

#============================================================================
# Configure JobStore
#============================================================================

org.quartz.jobStore.misfireThreshold = 60000

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.useProperties = true
org.quartz.jobStore.isClustered = true
org.quartz.jobStore.clusterCheckinInterval = 20000

org.quartz.scheduler.wrapJobExecutionInUserTranscation=false

cron.properties cron表达式配置文件,后期可以整改为数据库管理

cron.demo=0 * * * * ?
2、使用注解方式配置
//SchedulerConfig
package com.xxx.config.quartz;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import javax.sql.DataSource;

import org.quartz.Scheduler;
import org.quartz.Trigger;
import org.quartz.spi.JobFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.PropertiesFactoryBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.scheduling.quartz.CronTriggerFactoryBean;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;

/**
 * Spring Quartz配置
 *
 * @since 2021-01-16
 */
@Configuration
public class SchedulerConfig implements ApplicationContextAware {

    private ApplicationContext ctx;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.ctx = applicationContext;
    }

    /**
     * 目的是让所有的Job都能被 Autorwired
     * @param context
     * @return job factory
     */
    @Bean
    public JobFactory jobFactory(ApplicationContext context) {
        AutowiringSpringBeanJobFactory jobFactory = new AutowiringSpringBeanJobFactory();
        jobFactory.setApplicationContext(context);
        return jobFactory;
    }

    /**
     * SchedulerFactoryBean 配置类
     *
     * @since 8:58 2021/1/18
     * @param dataSource 数据源
     * @param jobFactory 此处的jobFactory已被重写,使用的是AutowiringSpringBeanJobFactory#createJobInstance 创建job
     * @return org.springframework.scheduling.quartz.SchedulerFactoryBean
     */
    @Bean
    public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource, JobFactory jobFactory) throws Exception {

        SchedulerFactoryBean factory = new SchedulerFactoryBean();

        factory.setDataSource(dataSource);
        factory.setOverwriteExistingJobs(true);
        factory.setJobFactory(jobFactory);

        factory.setStartupDelay(60);

        factory.setQuartzProperties(quartzProperties());
        factory.afterPropertiesSet();

        // 将所有CronTriggerFactoryBean类型的触发器都注册到SchedulerFactory
        Map<String, CronTriggerFactoryBean> triggerFactoryBeansMap =
            this.ctx.getBeansOfType(CronTriggerFactoryBean.class);
        List<Trigger> triggerBeansList = new ArrayList<>();
        triggerFactoryBeansMap.forEach((k, v) -> {
            triggerBeansList.add(v.getObject());
        });
        factory.setTriggers(triggerBeansList.toArray(new Trigger[triggerBeansList.size()]));
        return factory;
    }

    /**
     * quartz的自定义配置
     *
     * @since 15:53 2021/1/16
     * @param
     * @return java.util.Properties
     */
    @Bean
    public Properties quartzProperties() throws IOException {
        PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
        propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties"));
        propertiesFactoryBean.afterPropertiesSet();
        return propertiesFactoryBean.getObject();
    }

    /**
     *
     * @param schedulerFactoryBean
     * @return scheduler, 通过定时任务配置,随时rescheduler
     */

    @Bean
    public Scheduler scheduler(SchedulerFactoryBean schedulerFactoryBean) {
        return schedulerFactoryBean.getScheduler();
    }
}

//AutowiringSpringBeanJobFactory 解决job无法注入service问题

package com.xxx.config.quartz;

import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.scheduling.quartz.SpringBeanJobFactory;

/**
 * job实例在spring容器加载时候,能够注入bean,但是调度时,job对象会重新创建,此时就是导致已经注入的对象丢失,因此报空指针异常。
 * 通过重写JobFactory解决该问题
 *
 * @since 8:56 2021/1/18
 */
public class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory implements ApplicationContextAware {

    private transient AutowireCapableBeanFactory beanFactory;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        beanFactory = applicationContext.getAutowireCapableBeanFactory();
    }

    /**
     * 这里覆盖了super的createJobInstance方法,对其创建出来的类再进行autowire。
     *
     * @param bundle
     * @return
     * @throws Exception
     */
    @Override
    protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {
        final Object jobInstance = super.createJobInstance(bundle);
        beanFactory.autowireBean(jobInstance);
        return jobInstance;
    }
}



3、配置JobDetailFactoryBean和CronTriggerFactoryBean
package com.xxx.quartztask;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.scheduling.quartz.CronTriggerFactoryBean;
import org.springframework.scheduling.quartz.JobDetailFactoryBean;

import com.huawei.hms.mail.constant.JobName;
import com.huawei.hms.mail.quartzjob.QuartzDemoJob;

/**
 * Quartz 样例,构建CronTriggerFactoryBean
 *
 * @since 16:20 2021/1/16
 */
@Configuration
@PropertySource("classpath:cron.properties")
public class QuartzDemoTask {

    @Value("${cron.demo}")
    private String cron;

    @Bean(name = "quartzDemoJob")
    public JobDetailFactoryBean jobDetailFactoryBean() {
        JobDetailFactoryBean factoryBean = new JobDetailFactoryBean();
        factoryBean.setDurability(true);
        factoryBean.setJobClass(QuartzDemoJob.class);
        factoryBean.setName(JobName.DEMO_JOB);
        return factoryBean;
    }

    @Bean(name = "quartzDemoCronTrigger")
    public CronTriggerFactoryBean cronTriggerFactoryBean() {
        CronTriggerFactoryBean factoryBean = new CronTriggerFactoryBean();
        factoryBean.setJobDetail(jobDetailFactoryBean().getObject());
        factoryBean.setStartDelay(10);
        factoryBean.setCronExpression(cron);
        return factoryBean;
    }
}

4、Job类

@Slf4j
@Component
public class QuartzDemoJob implements Job {

    @Autowired
    private QuartsDemoService quartsDemoService;

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        quartsDemoService.sayHello();
    }
}

5、Service业务实现类

/**
 * Quartz 样例 定时任务 业务逻辑
 *
 * @since 2021-01-16
 */
@Service
public class QuartsDemoService {

    public void sayHello() {
        System.out.println("hello world");
    }
}

添加定时任务的步骤

1、新建Task类,构建JobDetailFactoryBean和CronTriggerFactoryBean并分别使用names属性唯一标识

2、在cron.properties中配置唯一命名的表达式,如cron.demo= xxx

3、新建Job类,需要实现Job接口,并重写execute方法,一般直接调用service实现

4、新建Servcie业务实现类,给Job类调用

5、声明job唯一名称JobName.DEMO_JOB常量

本博客用于技术学习,所有资源都来源于网络,部分是转发,部分是个人总结。欢迎共同学习和转载,转载请在醒目位置标明原文。如有侵权,请留言告知,及时撤除。
posted @ 2021-01-18 10:48  风动静泉  阅读(162)  评论(0编辑  收藏  举报