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"); } } } }