Feature
作业调度
当满足指定的触发器条件时,作业会被调度。触发器可以设置成在一天的特定时间(到毫秒)
在一周的某几天
在每月的某一天
在一年中的某些日子
特定时间除外的时间(如商业节假日除外)not on certain days listed within a registered Calendar (such as business holidays)
重复特定次数
重复进行,直到一个特定时间/日期
无限重复
重复一个延迟间隔
为了方便的在调度器中调度作业,作业和触发器都可以被命名或者组织到多个命名的组中。一个作业只能加入到调度器一次,但是可以跟多个触发器关联。
作业持久化
Quartz设计了一个JobStore的接口,可以通过不同实现方式实现持久化作业。
通过JDBSJobStore,所有的作业和触发器可以呗设置为"非易失的",进而存储在关系数据库中。
通过RAMJobStore,所有的作业和触发器可以被存储在内存中,这种情况下并不会实现存储话
The key interfaces of the Quartz API are:
Scheduler - the main API for interacting with the Scheduler.
Job - an interface to be implemented by components that you want the Scheduler to execute.
JobDetail - used to define instances of Jobs.
Trigger - a component that defines the schedule upon which a given Job will be executed.
JobBuilder - used to define/build JobDetail instances, which define instances of Jobs.TriggerBuilder - used to define/build Trigger instances.
Quartz定时任务默认都是并发执行的,不会等待上一次任务执行完毕,只要间隔时间到就会执行, 如果定时任执行太长,会长时间占用资源,导致其它任务堵塞。
1.在Spring中这时需要设置concurrent的值为false, 禁止并发执行。
1
|
<property name=
"concurrent"
value=
"true"
/>
|
2.当不使用spring的时候就需要在Job的实现类上加@DisallowConcurrentExecution的注释
@DisallowConcurrentExecution 禁止并发执行多个相同定义的JobDetail, 这个注解是加在Job类上的, 但意思并不是不能同时执行多个Job, 而是不能并发执行同一个Job Definition(由JobDetail定义), 但是可以同时执行多个不同的JobDetail, 举例说明,我们有一个Job类,叫做SayHelloJob, 并在这个Job上加了这个注解, 然后在这个Job上定义了很多个JobDetail, 如sayHelloToJoeJobDetail, sayHelloToMikeJobDetail, 那么当scheduler启动时, 不会并发执行多个sayHelloToJoeJobDetail或者sayHelloToMikeJobDetail, 但可以同时执行sayHelloToJoeJobDetail跟sayHelloToMikeJobDetail
@PersistJobDataAfterExecution 同样, 也是加在Job上,表示当正常执行完Job后, JobDataMap中的数据应该被改动, 以被下一次调用时用。当使用@PersistJobDataAfterExecution 注解时, 为了避免并发时, 存储数据造成混乱, 强烈建议把@DisallowConcurrentExecution注解也加上。
@DisallowConcurrentExecution
此标记用在实现Job的类上面,意思是不允许并发执行,按照我之前的理解是 不允许调度框架在同一时刻调用Job类,后来经过测试发现并不是这样,而是Job(任务)的执行时间[比如需要10秒]大于任务的时间间隔 [Interval(5秒)],那么默认情况下,调度框架为了能让 任务按照我们预定的时间间隔执行,会马上启用新的线程执行任务。否则的话会等待任务执行完毕以后 再重新执行!(这样会导致任务的执行不是按照我们预先定义的时间间隔执行)
测试代码,这是官方提供的例子。设定的时间间隔为3秒,但job执行时间是5秒,设置@DisallowConcurrentExecution以后程序会等任务执行完毕以后再去执行,否则会在3秒时再启用新的线程执行
集群特性
2、Quartz集群原理
2.1 Quartz 集群架构
一个Quartz集群中的每个节点是一个独立的Quartz应用,它又管理着其他的节点。这就意味着你必须对每个节点分别启动或停止。Quartz集群中,独立的Quartz节点并不与另一其的节点或是管理节点通信,而是通过相同的数据库表来感知到另一Quartz应用的,如图2.1所示。
图2.1 Quartz集群架构
2.2 Quartz集群相关数据库表
因为Quartz集群依赖于数据库,所以必须首先创建Quartz数据库表,Quartz发布包中包括了所有被支持的数据库平台的SQL脚本。这些SQL脚本存放于<quartz_home>/docs/dbTables 目录下。这里采用的Quartz 1.8.4版本,总共12张表,不同版本,表个数可能不同。数据库为mysql,用tables_mysql.sql创建数据库表。全部表如图2.2所示,对这些表的简要介绍如图2.3所示。
图2.2 Quartz 1.8.4在mysql数据库中生成的表
图2.3 Quartz数据表简介
2.2.1 调度器状态表(QRTZ_SCHEDULER_STATE)
说明:集群中节点实例信息,Quartz定时读取该表的信息判断集群中每个实例的当前状态。
instance_name:配置文件中org.quartz.scheduler.instanceId配置的名字,如果设置为AUTO,quartz会根据物理机名和当前时间产生一个名字。
last_checkin_time:上次检入时间
checkin_interval:检入间隔时间
2.2.2 触发器与任务关联表(qrtz_fired_triggers)
存储与已触发的Trigger相关的状态信息,以及相联Job的执行信息。
2.2.3 触发器信息表(qrtz_triggers)
trigger_name:trigger的名字,该名字用户自己可以随意定制,无强行要求
trigger_group:trigger所属组的名字,该名字用户自己随意定制,无强行要求
job_name:qrtz_job_details表job_name的外键
job_group:qrtz_job_details表job_group的外键
trigger_state:当前trigger状态设置为ACQUIRED,如果设为WAITING,则job不会触发
trigger_cron:触发器类型,使用cron表达式
2.2.4 任务详细信息表(qrtz_job_details)
说明:保存job详细信息,该表需要用户根据实际情况初始化
job_name:集群中job的名字,该名字用户自己可以随意定制,无强行要求。
job_group:集群中job的所属组的名字,该名字用户自己随意定制,无强行要求。
job_class_name:集群中job实现类的完全包名,quartz就是根据这个路径到classpath找到该job类的。
is_durable:是否持久化,把该属性设置为1,quartz会把job持久化到数据库中
job_data:一个blob字段,存放持久化job对象。
2.2.5权限信息表(qrtz_locks)
说明:tables_oracle.sql里有相应的dml初始化,如图2.4所示。
图2.4 Quartz权限信息表中的初始化信息
2.3 Quartz Scheduler在集群中的启动流程
Quartz Scheduler自身是察觉不到被集群的,只有配置给Scheduler的JDBC JobStore才知道。当Quartz Scheduler启动时,它调用JobStore的schedulerStarted()方法,它告诉JobStore Scheduler已经启动了。schedulerStarted() 方法是在JobStoreSupport类中实现的。JobStoreSupport类会根据quartz.properties文件中的设置来确定Scheduler实例是否参与到集群中。假如配置了集群,一个新的ClusterManager类的实例就被创建、初始化并启动。ClusterManager是在JobStoreSupport类中的一个内嵌类,继承了java.lang.Thread,它会定期运行,并对Scheduler实例执行检入的功能。Scheduler也要查看是否有任何一个别的集群节点失败了。检入操作执行周期在quartz.properties中配置。
2.4 侦测失败的Scheduler节点
当一个Scheduler实例执行检入时,它会查看是否有其他的Scheduler实例在到达他们所预期的时间还未检入。这是通过检查SCHEDULER_STATE表中Scheduler记录在LAST_CHEDK_TIME列的值是否早于org.quartz.jobStore.clusterCheckinInterval来确定的。如果一个或多个节点到了预定时间还没有检入,那么运行中的Scheduler就假定它(们) 失败了。
2.5 从故障实例中恢复Job
当一个Sheduler实例在执行某个Job时失败了,有可能由另一正常工作的Scheduler实例接过这个Job重新运行。要实现这种行为,配置给JobDetail对象的Job可恢复属性必须设置为true(job.setRequestsRecovery(true))。如果可恢复属性被设置为false(默认为false),当某个Scheduler在运行该job失败时,它将不会重新运行;而是由另一个Scheduler实例在下一次触发时间触发。Scheduler实例出现故障后多快能被侦测到取决于每个Scheduler的检入间隔(即2.3中提到的org.quartz.jobStore.clusterCheckinInterval)。