Quartz

Quartz

  Quartz是一个完全由Java编写的开源作业调度框架。不仅可以用来创建简单的定时程序,还可以创建成百上千甚至上万个Job的复杂定时程序。

  Quartz框架的核心对象:

    1、Job:表示一个工作,要执行的具体内容。此接口中只有一个方法,如下:void execute(JobExecutionContext context) 

    2、JobDetail:表示一个具体的可执行的调度程序,Job 是这个可执行程调度程序所要执行的内容,另外 JobDetail 还包含了这个任务调度的方案和策略。 

    3、Trigger触发器: 定义执行给定作业的计划的组件,即执行任务的规则, 代表一个调度参数的配置,什么时候去调。

    4、Scheduler:代表一个调度容器,一个调度容器中可以注册多个 JobDetail 和 Trigger。当 Trigger 与 JobDetail 组合,就可以被 Scheduler 容器调度了。

    5、JobBuilder - 用于定义/构建JobDetail实例,用于定义作业的实例。

    6、TriggerBuilder :用于定义/构建触发器实例。

 

  核心对象之间的关系:

 

  一句话总结其关系:

    Job表示一种类型的任务(执行逻辑相同,比如HTTP请求的,RPC接口请求的),Job下每个具体的任务详情存在JobDetail中,JobDetail中包含该任务的属性集和JobDataMap。Trigger与JobDetail绑定,是多对一的关系。所有的JobDetail和Trigger实例都会加载到scheduler中。

     当一个trigger被触发时,与之关联的JobDetail实例会被加载,JobDetail引用的job类通过配置在Scheduler上的JobFactory进行初始化。默认的JobFactory实现,仅仅是调用job类的newInstance()方法,然后尝试调用JobDataMap中的key的setter方法。

 

   主要线程:

  在Quartz中有两类线程,也即执行线程和调度线程,其中执行任务的线程通常用一个线程池维护。池中的线程越多,并发运行的jobs数越多。但是线程要根据系统和服务器配置适当的数量。一般评估一下在同一时间同时运行的jobs数量,与之相同即可。如果triggers的触发时间到达,并且没有可用的线程,Quartz将暂停直到线程可用,然后执行job,这甚至可能导致线程misfire - 如果在调度程序配置的“misfire阈值(默认60000毫秒,1分钟)”的持续时间内没有可用的线程。

  调度线程主要有两个:

regular Scheduler Thread(执行常规调度)和Misfire Scheduler Thread(执行错失的任务)。其中Regular Thread轮询Trigger,如果有将要触发的Trigger,则从任务线程池中获取一个

空闲线程,然后执行与该Trigger关联的Job。Misfire Thread则是扫描所有的Trigger,查看是否有错失的,如果有的话,根据一定的策略进行处理。

  数据存储:

  Quartz中的trigger和job需要存储下来才能被使用。Quartz中有两种存储方式:RAMJobStore,JobStoreSupport,其中RAMJobStore是将trigger和job存储在内存中,而JobStoreSupport是基于jdbc将trigger和job存储到数据库中。RAMJobStore的存取速度非常快,但是由于其在系统被停止后所有的数据都会丢失,所以在集群应用中,必须使用JobStoreSupport。

  表结构说明:

表名 用途
 BS_JOB_DETAILS  存储Job名称所属组等信息
 BS_TRIGGERS  存储Job触发类型(simple,cron),触发器名称(组+任务名),开始、结束时间,上次、下次触发时间;任务组件和任务参数等信息
 BS_SIMPLE_TRIGGERS  存储触发器名称,执行频率,重复策略,已触发次数等
 BS_CRON_TRIGGERS  存储触发器名称,cron表达式
 BS_TASK_HISTORY  存储任务执行历史,包括任务执行时间,执行日志,触发方式,完成时间等
   
   
   
   
   
   
   

  

  Quartz集群:

  Quartz集群中的每个节点都是一个独立的Quartz应用,并不与其他节点通信,而是通过相同的数据库表来感知其他节点的存在。

   

应用:

  quartz安装包根目录的lib/目录下有很多的jar包。其中,quartz-xxx.jar(其中xxx是版本号)是最主要的。为了使用quartz,必须将该jar包放在应用的classpath下

  属性文件:quartz使用名为quartz.properties的配置文件。刚开始时该配置文件不是必须的,但是为了使用最基本的配置,该文件必须位于classpath下。

  主要的配置:

属性名称 说明 是否必填 值类型 默认值
org.quartz.scheduler.instanceName 应用实例名称 否   String 'QuartzScheduler'
org.quartz.scheduler.instanceId 应用实例编号,需唯一 String 'NON_CLUSTERED'
org.quartz.scheduler.threadName 调度器线程名称 String instanceName
org.quartz.threadPool.threadCount 线程池数量,决定可以同时运行多少Job int -1
org.quartz.jobStore.driverDelegateClass 数据库驱动策略(MySQL或其他) String null
org.quartz.jobStore.dataSource 数据源 String null
org.quartz.jobStore.tablePrefix 数据库表名的前缀 String "QRTZ_"
org.quartz.jobStore.useProperties JobDataMap中的值以键值对存储还是二进制 boolean false
org.quartz.jobStore.misfireThreshold 被错失执行的Job下一次触发的间隔 int 60000
org.quartz.jobStore.isClustered 是否集群 boolean false

 

   API:

  Scheduler:与调度程序交互的主要API

  Job:由希望由调度程序执行的组件实现的接口,一个job就是一个实现了Job接口的类

  JobDetail:用于自定义作业的实例

  Trigger:触发器,定义执行作业的计划的组件

  JobBuilder:用于定义/构建JobDetail实例,即用于定义作业的实例

  TriggerBuilder:用于定义/构建触发器实例

  

   Scheduler的生命期,从SchedulerFactory创建它时开始,到Scheduler调用shutdown()方法时结束;Scheduler被创建后,可以增加、删除和列举Job和Trigger,以及执行其它与调度相关的操作(如暂停Trigger)。但是,Scheduler只有在调用start()方法后,才会真正地触发trigger(即执行job)。

   当Job的一个trigger被触发时,execute()方法由调度程度的一个工作线程调用。传递给execute()方法的JobExecutionContext对象中保存着该job运行时的一些信息,执行job的schedule的引用,触发job的trigger引用,JobDetail对象引用,以及一些其他信息。

   JobDetail对象是在将job加入scheduler时,由客户端程序(你的程序)创建的。它包含job的各种属性设置,以及用于存储job实例状态信息的JobDataMap

   Trigger用于触发Job的执行。当你准备调度一个job时,你创建一个Trigger的实例,然后设置调度相关的属性。Trigger也有一个相关联的JobDataMap,用于给Job传递一些触发相关的参数。Quartz自带了各种不同类型的Trigger,最常用的主要是SimpleTrigger和CronTrigger。SimpleTrigger主要用于一次性执行的Job(只在某个特定的时间点执行一次),或者Job在特定的时间点执行,重复执行N次,每次执行间隔T个时间单位。CronTrigger在基于日历的调度上非常有用,如“每个星期五的正午”,或者“每月的第十天的上午10:15”等。cron(秒分时日月周年)

   Job被创建后,可以保存在Scheduler中,与Trigger是独立的,同一个Job可以有多个Trigger;这种松耦合的另一个好处是,当与Scheduler中的Job关联的trigger都过期时,可以配置Job稍后被重新调度,而不用重新定义Job;还有,可以修改或者替换Trigger,而不用重新定义与之关联的Job

 

  Scheduler:

  Scheduler实例化后,可以启动(start)、暂停(stand-by)、停止(shutdown)。注意:scheduler被停止后,除非重新实例化,否则不能重新启动;只有当scheduler启动后,即使处于暂停状态也不行,trigger才会被触发(job才会被执行)

  Key:
  将Job和Trigger注册到Scheduler时,可以为它们设置key,配置其身份属性。Job和Trigger的key(JobKey和TriggerKey)可以用于将Job和Trigger放到不同的分组(group)里,然后基于分组进行操作。同一个分组下的Job或Trigger的名称必须唯一,即一个Job或Trigger的key由名称(name)和分组(group)组成。

  Job:

  定义了一个实现Job接口的类,仅仅表明了该job需要完成什么类型的任务(如HTTP调用类型的任务),除此之外,Quartz还需要知道该Job实例所包含的属性,这都是JobDetail来完成。我们传给scheduler一个JobDetail实例,因为我们在创建JobDetail时,将要执行的job的类名传给了JobDetail,所以scheduler就知道了要执行何种类型的job;每次当scheduler执行job时,在调用execute()方法之前会创建该类的一个新的实例;执行完毕,对该实例的引用就被丢弃了,实例会被垃圾回收,这种执行策略要求在job类中不应该定义有状态的数据属性,因为在job的多次执行中,这些属性的值不会保留。因此在job的多次执行中,其状态就存放在JobDataMap中,JobDetail对象的一部分。

  JobDataMap:

  JobDataMap中可以包含不限量的(序列化的)数据对象,在job实例执行的时候,可以使用其中的数据;JobDataMap是Java Map接口的一个实现,额外增加了一些便于存取基本类型的数据的方法。将job加入到scheduler之前,在构建JobDetail时,可以将数据放入JobDataMap。在job的执行过程中,可以从JobDataMap中取出数据。

在Job执行时,JobExecutionContext是JobDetail中的JobDataMap和Trigger中的JobDataMap的并集,但是如果存在相同的数据,则后者会覆盖前者的值。从JobExecutionContext中获取JobDataMap:JobDataMap dataMap = context.getMergedJobDataMap();  

  Trigger:

  其属性有:jobKey,startTime,endTime,优先级,错过触发,日历等。

  jobKey:当trigger被触发时执行的job的身份,

  startTime:trigger第一次触发的时间,默认当前时间,endTime:trigger失效的时间,默认为null,表示永不失效。

  优先级(priority):当trigger比quartz线程池中的线程多时,通过配置优先级可以控制让优先级高的trigger先被触发,只有同时触发的trigger之间会比较优先级,默认值为5。

  错过触发(misfire Instructions):如果scheduler关闭了,或者Quartz线程池中没有可用的线程来执行job,此时持久性的trigger就会错过(miss)其触发时间,即错过触发(misfire)。不同类型的trigger,有不同的misfire机制。它们默认都使用“智能机制(smart policy)”,即根据trigger的类型和配置动态调整行为(SimpleTrigger会根据实例的配置及状态,在所有MISFIRE策略中动态选择一种Misfire策略;CronTrigger 的智能策略为MISFIRE_INSTRUCTION_FIRE_NOW)。当scheduler启动的时候,查询所有错过触发(misfire)的持久性trigger。然后根据它们各自的misfire机制更新trigger的信息。

    cronTrigger misfire策略:

       SmartPolicy:默认策略。对应的是FireAndProceed策略

       IgnoreMisfires:以错过的第一个频率时间立刻开始执行,重做错过的所有频率周期后,当下一次触发频率发生时间大于当前时间后,再按照正常的Cron频率依次执行

       FireAndProceed:以当前时间为触发频率立刻触发一次执行,然后按照Cron频率依次执行

       DoNothing:不触发立即执行,等待下次Cron触发频率到达时刻开始按照Cron频率依次执行

 

  监听器:

  Listeners是我们创建的用来根据调度程序中发生的事件执行操作。通过实现TriggerListener接口或者JobListener,然后在运行时向调度程度scheduler注册,并且给出一个名称(重写getName方法)。如:scheduler.getListenerManager().addJobListener

  TriggerListeners:接收与触发器相关的事件,包括触发器触发,触发失灵,触发完成。

  JobListeners:接收与job相关的事件,包括job即将执行的通知,以及job完成执行时的通知

  SchedulerListeners:接收添加job/触发器,删除job/触发器,调度程序中的严重错误,关闭调度程序的通知等。实现SchedulerListener 接口,scheduler.getListenerManager().addSchedulerListener。

 

 

SpringBoot Quartz

1、jar包依赖

     <!--quartz-->
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
        </dependency>
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz-jobs</artifactId>
        </dependency>

2、配置 Quartz Scheduler

   (1):spring-boot-autoconfigure 提供了quartz的自动配置类

/**
 * {@link EnableAutoConfiguration Auto-configuration} for Quartz Scheduler.
 *
 * @author Vedran Pavic
 * @author Stephane Nicoll
 * @since 2.0.0
 */
@Configuration
@ConditionalOnClass({ Scheduler.class, SchedulerFactoryBean.class,PlatformTransactionManager.class })
@EnableConfigurationProperties(QuartzProperties.class)
@AutoConfigureAfter({ DataSourceAutoConfiguration.class,HibernateJpaAutoConfiguration.class })
public class QuartzAutoConfiguration {

    private final QuartzProperties properties;

    private final ObjectProvider<SchedulerFactoryBeanCustomizer> customizers;

    private final JobDetail[] jobDetails;

    private final Map<String, Calendar> calendars;

    private final Trigger[] triggers;

    private final ApplicationContext applicationContext;

    public QuartzAutoConfiguration(QuartzProperties properties,
            ObjectProvider<SchedulerFactoryBeanCustomizer> customizers,
            ObjectProvider<JobDetail[]> jobDetails,
            ObjectProvider<Map<String, Calendar>> calendars,
            ObjectProvider<Trigger[]> triggers, ApplicationContext applicationContext) {
        this.properties = properties;
        this.customizers = customizers;
        this.jobDetails = jobDetails.getIfAvailable();
        this.calendars = calendars.getIfAvailable();
        this.triggers = triggers.getIfAvailable();
        this.applicationContext = applicationContext;
    }

    @Bean
    @ConditionalOnMissingBean
    public SchedulerFactoryBean quartzScheduler() {
        SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
        SpringBeanJobFactory jobFactory = new SpringBeanJobFactory();
        jobFactory.setApplicationContext(this.applicationContext);
        schedulerFactoryBean.setJobFactory(jobFactory);
        if (this.properties.getSchedulerName() != null) {
            schedulerFactoryBean.setSchedulerName(this.properties.getSchedulerName());
        }
        schedulerFactoryBean.setAutoStartup(this.properties.isAutoStartup());
        schedulerFactoryBean.setStartupDelay((int) this.properties.getStartupDelay().getSeconds());
        schedulerFactoryBean.setWaitForJobsToCompleteOnShutdown(
                this.properties.isWaitForJobsToCompleteOnShutdown());
        schedulerFactoryBean.setOverwriteExistingJobs(this.properties.isOverwriteExistingJobs());
        if (!this.properties.getProperties().isEmpty()) {
            schedulerFactoryBean.setQuartzProperties(asProperties(this.properties.getProperties()));
        }
        if (this.jobDetails != null && this.jobDetails.length > 0) {
            schedulerFactoryBean.setJobDetails(this.jobDetails);
        }
        if (this.calendars != null && !this.calendars.isEmpty()) {
            schedulerFactoryBean.setCalendars(this.calendars);
        }
        if (this.triggers != null && this.triggers.length > 0) {
            schedulerFactoryBean.setTriggers(this.triggers);
        }
        customize(schedulerFactoryBean);
        return schedulerFactoryBean;
    }

    private Properties asProperties(Map<String, String> source) {
        Properties properties = new Properties();
        properties.putAll(source);
        return properties;
    }

    private void customize(SchedulerFactoryBean schedulerFactoryBean) {
        this.customizers.orderedStream().forEach((customizer) -> customizer.customize(schedulerFactoryBean));
    }

    @Configuration
    @ConditionalOnSingleCandidate(DataSource.class)
    protected static class JdbcStoreTypeConfiguration {

        @Bean
        @Order(0)
        public SchedulerFactoryBeanCustomizer dataSourceCustomizer(
                QuartzProperties properties, DataSource dataSource,
                @QuartzDataSource ObjectProvider<DataSource> quartzDataSource,
                ObjectProvider<PlatformTransactionManager> transactionManager) {
            return (schedulerFactoryBean) -> {
                if (properties.getJobStoreType() == JobStoreType.JDBC) {
                    DataSource dataSourceToUse = getDataSource(dataSource,quartzDataSource);
                    schedulerFactoryBean.setDataSource(dataSourceToUse);
                    PlatformTransactionManager txManager = transactionManager.getIfUnique();
                    if (txManager != null) {
                        schedulerFactoryBean.setTransactionManager(txManager);
                    }
                }
            };
        }

        private DataSource getDataSource(DataSource dataSource,ObjectProvider<DataSource> quartzDataSource) {
            DataSource dataSourceIfAvailable = quartzDataSource.getIfAvailable();
            return (dataSourceIfAvailable != null) ? dataSourceIfAvailable : dataSource;
        }

        @Bean
        @ConditionalOnMissingBean
        public QuartzDataSourceInitializer quartzDataSourceInitializer(
                DataSource dataSource,
                @QuartzDataSource ObjectProvider<DataSource> quartzDataSource,
                ResourceLoader resourceLoader, QuartzProperties properties) {
            DataSource dataSourceToUse = getDataSource(dataSource, quartzDataSource);
            return new QuartzDataSourceInitializer(dataSourceToUse, resourceLoader,
                    properties);
        }

        @Bean
        public static DataSourceInitializerSchedulerDependencyPostProcessor dataSourceInitializerSchedulerDependencyPostProcessor() {
            return new DataSourceInitializerSchedulerDependencyPostProcessor();
        }

        private static class DataSourceInitializerSchedulerDependencyPostProcessor
                extends AbstractDependsOnBeanFactoryPostProcessor {

            DataSourceInitializerSchedulerDependencyPostProcessor() {
                super(Scheduler.class, SchedulerFactoryBean.class,
                        "quartzDataSourceInitializer");
            }

        }

    }

}
posted @ 2019-12-06 15:14  杨岂  阅读(909)  评论(0编辑  收藏  举报