• 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");
                }
    
            }
    
        }
    
    }
  • 相关阅读:
    JavaScript
    格式与布局
    表单和样式表
    HTML中表格的使用
    HTML 基础
    foreach使用和函数
    20160423 二维数组,锯齿数组和集合
    【学习笔记】系统集成项目管理
    BSEG和BSIS、BSAS、BSID、BSAD、BSIK、BSAK六个表的关系(转)
    关于ABAP事件的一张图
  • 原文地址:https://www.cnblogs.com/yangyongjie/p/11995415.html
Copyright © 2020-2023  润新知