实践Quartz

Scheduler

好比一个容器,可以装入JobDetail和Trigger,可以设定Trigger触发JobDetail

SchedulerFactory

Property Name Type Default Value
org.quartz.scheduler.instanceName string ‘QuartzScheduler’
org.quartz.scheduler.instanceId string ‘NON_CLUSTERED’
org.quartz.scheduler.instanceIdGenerator.class string (class name) org.quartz.simpl.SimpleInstanceIdGenerator
org.quartz.scheduler.threadName string instanceName + ‘_QuartzSchedulerThread’
org.quartz.scheduler.makeSchedulerThreadDaemon boolean false
org.quartz.scheduler .threadsInheritContextClassLoaderOfInitializer boolean false
org.quartz.scheduler.idleWaitTime long 30000
org.quartz.scheduler.dbFailureRetryInterval long 15000
org.quartz.scheduler.classLoadHelper.class string (class name) org.quartz.simpl.CascadingClassLoadHelper
org.quartz.scheduler.jobFactory.class string (class name) org.quartz.simpl.PropertySettingJobFactory
org.quartz.context.key.SOME_KEY string none
org.quartz.scheduler.userTransactionURL string (url) ‘java:comp/UserTransaction’
org.quartz.scheduler.wrapJobExecutionInUserTransaction boolean false
org.quartz.scheduler.skipUpdateCheck boolean false
org.quartz.scheduler.batchTriggerAcquisitionMaxCount int 1
org.quartz.scheduler.batchTriggerAcquisitionFireAheadTimeWindow long 0

org.quartz.scheduler.instanceName

Can be any string, and the value has no meaning to the scheduler itself - but rather serves as a mechanism for clientcode to distinguish schedulers when multiple instances are used within the same program. If you are using the clusteringfeatures, you must use the same name for every instance in the cluster that is ‘logically’ the same Scheduler.

org.quartz.scheduler.instanceId

Can be any string, but must be unique for all schedulers working as if they are the same ‘logical’ Scheduler within acluster. You may use the value “AUTO” as the instanceId if you wish the Id to be generated for you. Or the value“SYS_PROP” if you want the value to come from the system property “org.quartz.scheduler.instanceId”.

org.quartz.scheduler.instanceIdGenerator.class

Only used if org.quartz.scheduler.instanceId is set to “AUTO”. Defaults to“org.quartz.simpl.SimpleInstanceIdGenerator”, which generates an instance id based upon host name and time stamp.Other IntanceIdGenerator implementations include SystemPropertyInstanceIdGenerator (which gets the instance idfrom the system property “org.quartz.scheduler.instanceId”, and HostnameInstanceIdGenerator which uses thelocal host name (InetAddress.getLocalHost().getHostName()). You can also implement the InstanceIdGeneratorinterface your self.

org.quartz.scheduler.threadName

Can be any String that is a valid name for a java thread. If this property is not specified, the thread will receive thescheduler’s name (“org.quartz.scheduler.instanceName”) plus an the appended string ‘_QuartzSchedulerThread’.

org.quartz.scheduler.makeSchedulerThreadDaemon

A boolean value (‘true’ or ‘false’) that specifies whether the main thread of the scheduler should be a daemon thread ornot. See also the org.quartz.scheduler.makeSchedulerThreadDaemon property for tuning the SimpleThreadPool if that is the thread pool implementationyou are using (which is most likely the case).

org.quartz.scheduler.threadsInheritContextClassLoaderOfInitializer

A boolean value (‘true’ or ‘false’) that specifies whether the threads spawned by Quartz will inherit the contextClassLoader of the initializing thread (thread that initializes the Quartz instance). This will affect Quartz mainscheduling thread, JDBCJobStore’s misfire handling thread (if JDBCJobStore is used), cluster recovery thread (ifclustering is used), and threads in SimpleThreadPool (if SimpleThreadPool is used). Setting this value to ‘true’ mayhelp with class loading, JNDI look-ups, and other issues related to using Quartz within an application server.

org.quartz.scheduler.idleWaitTime

Is the amount of time in milliseconds that the scheduler will wait before re-queries for available triggers when thescheduler is otherwise idle. Normally you should not have to ‘tune’ this parameter, unless you’re using XA transactions,and are having problems with delayed firings of triggers that should fire immediately. Values less than 5000 ms are notrecommended as it will cause excessive database querying. Values less than 1000 are not legal.

org.quartz.scheduler.dbFailureRetryInterval

Is the amount of time in milliseconds that the scheduler will wait between re-tries when it has detected a loss ofconnectivity within the JobStore (e.g. to the database). This parameter is obviously not very meaningful when usingRamJobStore.

org.quartz.scheduler.classLoadHelper.class

Defaults to the most robust approach, which is to use the “org.quartz.simpl.CascadingClassLoadHelper” class - which inturn uses every other ClassLoadHelper class until one works. You should probably not find the need to specify any otherclass for this property, though strange things seem to happen within application servers. All of the current possibleClassLoadHelper implementation can be found in the org.quartz.simpl package.

org.quartz.scheduler.jobFactory.class

The class name of the JobFactory to use. A JobFatcory is responsible for producing instances of JobClasses.The default is ‘org.quartz.simpl.PropertySettingJobFactory’, which simply calls newInstance() on the class to producea new instance each time execution is about to occur. PropertySettingJobFactory also reflectivelysets the job’s bean properties using the contents of the SchedulerContext and Job and Trigger JobDataMaps.

org.quartz.context.key.SOME_KEY

Represent a name-value pair that will be placed into the “scheduler context” as strings. (see Scheduler.getContext()).So for example, the setting “org.quartz.context.key.MyKey = MyValue” would perform the equivalent ofscheduler.getContext().put(“MyKey”, “MyValue”).

The Transaction-Related properties should be left out of the config file unless you are using JTA transactions.

org.quartz.scheduler.userTransactionURL

Should be set to the JNDI URL at which Quartz can locate the Application Server’s UserTransaction manager. The defaultvalue (if not specified) is “java:comp/UserTransaction” - which works for almost all Application Servers. Websphereusers may need to set this property to “jta/usertransaction”. This is only used if Quartz is configured to useJobStoreCMT, and org.quartz.scheduler.wrapJobExecutionInUserTransaction is set to true.

org.quartz.scheduler.wrapJobExecutionInUserTransaction

Should be set to “true” if you want Quartz to start a UserTransaction before calling execute on your job. The Tx willcommit after the job’s execute method completes, and after the JobDataMap is updated (if it is a StatefulJob). Thedefault value is “false”. You may also be interested in using the @ExecuteInJTATransaction annotationon your job class, which lets you control for an individual job whether Quartz should start a JTA transaction -whereas this property causes it to occur for all jobs.

org.quartz.scheduler.skipUpdateCheck

Whether or not to skip running a quick web request to determine if there is an updated version of Quartz available fordownload. If the check runs, and an update is found, it will be reported as available in Quartz’s logs. Youcan also disable the update check with the system property “org.terracotta.quartz.skipUpdateCheck=true” (whichyou can set in your system environment or as a -D on the java command line). It is recommended that you disablethe update check for production deployments.

org.quartz.scheduler.batchTriggerAcquisitionMaxCount

The maximum number of triggers that a scheduler node is allowed to acquire (for firing) at once. Default valueis 1. The larger the number, the more efficient firing is (in situations where there are very many triggers needing tobe fired all at once) - but at the cost of possible imbalanced load between cluster nodes. If the value of thisproperty is set to > 1, and JDBC JobStore is used, then the property “org.quartz.jobStore.acquireTriggersWithinLock”must be set to “true” to avoid data corruption.

org.quartz.scheduler.batchTriggerAcquisitionFireAheadTimeWindow

The amount of time in milliseconds that a trigger is allowed to be acquired and fired ahead of its scheduled fire time. Defaults to 0. The larger the number, the more likely batch acquisition of triggers to fire will be able to select and fire more than 1 trigger at a time - at the cost of trigger schedule not being honored precisely (triggers may fire this amount early). This may be useful (for performance’s sake) in situations where the scheduler has very large numbers of triggers that need to be fired at or near the same time.

unschedule

1
scheduler.unscheduleJob(TriggerKey.triggerKey("trigger1","group1"));

Job

具体任务接口

JobExecutionContext

DisallowConcurrentExecution

不允许Job并发执行的注解,默认情况下Job是允许并发执行的

PersistJobDataAfterExecution

持久化JobData的注解

JobDetail

对Job的包装,同一个JobDetail可以被多个Trigger关联。

JobKey:由namegroup确定JobDetail的唯一性,group默认值DEFAULT,否则报错如下:

1
org.quartz.ObjectAlreadyExistsException: Unable to store Job : 'group1.job1', because one already exists with this identification.

JobBuilder:

JobClass:

JobDataMap:存储Job参数等信息

isDurable:当没有trigger关联时,是否持久化,比如unschedule后,trigger不存在了,JobDetail还在schedule中。

requestsRecovery:

Trigger

startTime:起始时间,默认当前时间,会自动运行一次

endTime:截止时间,

TriggerKey:由namegroup确定Trigger的唯一性,group默认值DEFAULT,否则报错如下:

1
org.quartz.ObjectAlreadyExistsException: Unable to store Trigger with name: 'trigger1' and group: 'group1', because one already exists with this identification.

Trigger可以直接设定JobDetail

JobDataMap:存储Job参数等信息,优先级高于JobDetail中的JobDataMap,实际执行时传递给Job的是merge后的JobDataMap

forJob:设定关联的JobDetail,优先级高于schedule指定的JobDetail

TriggerBuilder

用于构建Trigger

ScheduleBuilder

Quartz SchedulerBuilder

SimpleScheduleBuilder

1
2
3
4
5
// 间隔,单位毫秒
private long interval = 0;
// 次数,-1代表永远
private int repeatCount = 0;
private int misfireInstruction = SimpleTrigger.MISFIRE_INSTRUCTION_SMART_POLICY;

CronScheduleBuilder

1
2
private CronExpression cronExpression;
private int misfireInstruction = CronTrigger.MISFIRE_INSTRUCTION_SMART_POLICY;

DailyTimeIntervalScheduleBuilder

基于周几的间隔调度

1
2
3
4
5
6
7
8
9
10
private int interval = 1;
// 只支持时分秒
private IntervalUnit intervalUnit = IntervalUnit.MINUTE;
// 周几
private Set<Integer> daysOfWeek;
private TimeOfDay startTimeOfDay;
private TimeOfDay endTimeOfDay;
private int repeatCount = DailyTimeIntervalTrigger.REPEAT_INDEFINITELY;

private int misfireInstruction = CalendarIntervalTrigger.MISFIRE_INSTRUCTION_SMART_POLICY;

CalendarIntervalScheduleBuilder

基于日历的间隔调度(日历是不均匀的,每月天数不同,每隔5个月cron做不到(5不能被12整除))

1
2
3
4
5
6
7
private int interval = 1;
private IntervalUnit intervalUnit = IntervalUnit.DAY;

private int misfireInstruction = CalendarIntervalTrigger.MISFIRE_INSTRUCTION_SMART_POLICY;
private TimeZone timeZone;
private boolean preserveHourOfDayAcrossDaylightSavings;
private boolean skipDayIfHourDoesNotExist;

DateBuilder

Quartz Trigger

SimpleTrigger

CronTrigger

DailyTimeIntervalTrigger

CalendarIntervalTrigger

JobStore

Quartz JobStore

store scheduling information (job, triggers and calendars)

sql脚本 数据库相关脚步在quartz-2.3.0.jar/org/quartz/impl/jdbcjobstore/目录下,比如:tables_mysql_innodb.sql。 共计11张表。

1
2
private static final String DEFAULT_SCHEMA_LOCATION = "classpath:org/quartz/impl/"
				+ "jdbcjobstore/tables_@@platform@@.sql";
1
2
3
4
# for fully JDBC-compliant drivers
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
# oracle
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.oracle.OracleDelegat

JobStoreTX

非JTA

JobStoreCMT

容器环境,使用JTA,需要两个数据源,一个容器管理,一个自用

Delegates

1
2
3
4
# 

# oracle
spring.quartz.properties.org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.oracle.OracleDelegate

Datasources

ConnectionProvider

自带支持c3p0、HikariCP、JNDI和weblogic

ThreadPool

Listener

ListenerManager

Matcher

SchedulerListener

TriggerListener

Configuring a Global TriggerListener

1
2
3
org.quartz.triggerListener.NAME.class = com.foo.MyListenerClass
org.quartz.triggerListener.NAME.propName = propValue
org.quartz.triggerListener.NAME.prop2Name = prop2Value

JobListener

Configuring a Global JobListener

1
2
3
org.quartz.jobListener.NAME.class = com.foo.MyListenerClass
org.quartz.jobListener.NAME.propName = propValue
org.quartz.jobListener.NAME.prop2Name = prop2Value

Plugins

SchedulerPlugin

quartz.properties

2.3.0 default

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Default Properties file for use by StdSchedulerFactory
# to create a Quartz Scheduler Instance, if a different
# properties file is not explicitly specified.
#

org.quartz.scheduler.instanceName: DefaultQuartzScheduler
org.quartz.scheduler.rmi.export: false
org.quartz.scheduler.rmi.proxy: false
org.quartz.scheduler.wrapJobExecutionInUserTransaction: false

org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount: 10
org.quartz.threadPool.threadPriority: 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true

org.quartz.jobStore.misfireThreshold: 60000

org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore

Cluster

Quartz Cluster

Quartz集群通过fail-over和负载均衡可以带来高可用和可扩展。

通过org.quartz.jobStore.isClustered=true开启集群。

多个实例的org.quartz.scheduler.instanceId属性值不能一样(也可以设置为AUTO(自动分配)),其他都可以一样

必须选择持久化JobStoreJobStoreTXJobStoreCMT),共享同一个数据库(抢锁)。

org.quartz.jobStore.clusterCheckinInterval

负载均衡:所有节点随机并行调度执行job,当job较多时负载机制为线性随机,当job较少时负载机制优先跟之前相同的节点

fail-over:对于recovery job,当其在调度执行时所在节点宕机了,其他节点会检测到后会重新调度执行,对于非recovery job则不会重新执行

Quartz集群最适合长时间运行任务或cpu密集型job。对于大量短时间的job,考虑将任务分区到不同的scheduler

集群实例如果在不同的服务器,必须保持时钟同步(秒级)

样例

启动日志会标示是否集群(is not clustered或is clustered)。

1
2
3
4
5
6
7
2018-10-30 15:02:30.038  INFO 20151 --- [           main] org.quartz.core.QuartzScheduler          : Scheduler meta-data: Quartz Scheduler (v2.3.0) 'quartzScheduler' with instanceId 'ray-pc1540882949765'
  Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.
  NOT STARTED.
  Currently in standby mode.
  Number of jobs executed: 0
  Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 10 threads.
  Using job-store 'org.springframework.scheduling.quartz.LocalDataSourceJobStore' - which supports persistence. and is clustered.

节点一

1
2
3
4
5
6
7
8
2018-06-15 14:37:15.018  INFO 5090 --- [eduler_Worker-6] wang.ray.sample.quartz.MyJob             : Hello World!  MyJob is executing.
2018-06-15 14:37:17.115  INFO 5090 --- [       Thread-4] s.c.a.AnnotationConfigApplicationContext : Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@470734c3: startup date [Fri Jun 15 14:32:27 CST 2018]; root of context hierarchy
2018-06-15 14:37:17.116  INFO 5090 --- [       Thread-4] o.s.c.support.DefaultLifecycleProcessor  : Stopping beans in phase 2147483647
2018-06-15 14:37:17.117  INFO 5090 --- [       Thread-4] org.quartz.core.QuartzScheduler          : Scheduler quartzScheduler_$_angi-deepin1529044349144 paused.
2018-06-15 14:37:17.118  INFO 5090 --- [       Thread-4] o.s.s.quartz.SchedulerFactoryBean        : Shutting down Quartz Scheduler
2018-06-15 14:37:17.118  INFO 5090 --- [       Thread-4] org.quartz.core.QuartzScheduler          : Scheduler quartzScheduler_$_angi-deepin1529044349144 shutting down.
2018-06-15 14:37:17.118  INFO 5090 --- [       Thread-4] org.quartz.core.QuartzScheduler          : Scheduler quartzScheduler_$_angi-deepin1529044349144 paused.
2018-06-15 14:37:17.118  INFO 5090 --- [       Thread-4] org.quartz.core.QuartzScheduler          : Scheduler quartzScheduler_$_angi-deepin1529044349144 shutdown complete.

节点二

1
2
3
4
5
6
7
8
9
10
11
12
13
2018-06-15 14:35:24.971  INFO 5392 --- [           main] w.r.s.q.SampleQuartzApplicationTests     : Started SampleQuartzApplicationTests in 1.725 seconds (JVM running for 2.722)
2018-06-15 14:37:18.602  INFO 5392 --- [eduler_Worker-1] wang.ray.sample.quartz.MyJob             : Hello World!  MyJob is executing.
2018-06-15 14:37:21.241  INFO 5392 --- [eduler_Worker-2] wang.ray.sample.quartz.MyJob             : Hello World!  MyJob is executing.
2018-06-15 14:37:24.016  INFO 5392 --- [eduler_Worker-3] wang.ray.sample.quartz.MyJob             : Hello World!  MyJob is executing.
2018-06-15 14:37:27.226  INFO 5392 --- [eduler_Worker-4] wang.ray.sample.quartz.MyJob             : Hello World!  MyJob is executing.
2018-06-15 14:37:30.135  INFO 5392 --- [eduler_Worker-5] wang.ray.sample.quartz.MyJob             : Hello World!  MyJob is executing.
2018-06-15 14:37:33.025  INFO 5392 --- [eduler_Worker-6] wang.ray.sample.quartz.MyJob             : Hello World!  MyJob is executing.
2018-06-15 14:37:36.703  INFO 5392 --- [eduler_Worker-7] wang.ray.sample.quartz.MyJob             : Hello World!  MyJob is executing.
2018-06-15 14:37:39.277  INFO 5392 --- [eduler_Worker-8] wang.ray.sample.quartz.MyJob             : Hello World!  MyJob is executing.
2018-06-15 14:37:42.029  INFO 5392 --- [eduler_Worker-9] wang.ray.sample.quartz.MyJob             : Hello World!  MyJob is executing.
2018-06-15 14:37:44.971  INFO 5392 --- [_ClusterManager] o.s.s.quartz.LocalDataSourceJobStore     : ClusterManager: detected 1 failed or restarted instances.
2018-06-15 14:37:44.971  INFO 5392 --- [_ClusterManager] o.s.s.quartz.LocalDataSourceJobStore     : ClusterManager: Scanning for instance "angi-deepin1529044349144"'s failed in-progress jobs.
2018-06-15 14:37:45.037  INFO 5392 --- [duler_Worker-10] wang.ray.sample.quartz.MyJob             : Hello World!  MyJob is executing.
1
2019-01-15 17:57:27.517  WARN 10345 --- [_ClusterManager] o.s.s.quartz.LocalDataSourceJobStore     : This scheduler instance (Ray-TR1547546121790) is still active but was recovered by another instance in the cluster.  This may cause inconsistent behavior.

原因:cluster中各机器之间的时间不同步。 应同步各机器时间,另设置cluster-checkin iterval。 The systems are syncronized via NTP。

MISFIRE

当一个持久的触发器因为调度器被关闭,或者线程池中没有可用的线程,或者因为DisallowConcurrentExecution且Job执行时间过长等而错过了激活时间时,即没有在预计的时间触发执行,就可能会发生激活失败(misfire)

1
2
2018-06-15 15:45:09.100  INFO 8491 --- [_MisfireHandler] o.s.s.quartz.LocalDataSourceJobStore     : Handling 1 trigger(s) that missed their scheduled fire-time.
2018-06-15 15:45:22.179  INFO 8491 --- [eduler_Worker-9] wang.ray.sample.quartz.MyJob             : Hello World!  MyJob is executing.

misfire判定

实际执行时间与预定的执行时间相差>misfireThreshold,则判定为misfire

1
org.quartz.jobStore.misfireThreshold=60000

misfire instruction

发生misfire后如何应对处理呢?misfire instruction就是用来定义处理策略的。

Trigger

1
2
3
// 智能策略,具体交由特定Trigger的updateAfterMisfire()中选定
public static final int MISFIRE_INSTRUCTION_SMART_POLICY = 0;
public static final int MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY = -1;

SimpleTrigger

updateAfterMisfire()

1
2
3
4
5
6
如果Repeat Count=0
	instruction selected = MISFIRE_INSTRUCTION_FIRE_NOW;
如果Repeat Count=REPEAT_INDEFINITELY
	instruction selected = MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT;
如果Repeat Count>0
	instruction selected = MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT;

NOW:立即执行,同时重新开始按计划执行

NEXT:不立即执行,以当前时间为基准,重新开始按计划执行

WITH_REMAINING_COUNT:指超时期内错过的执行机会作废

WITH_EXISTING_COUNT:指超时期内错过的执行机会不作废

1
2
3
4
5
public static final int MISFIRE_INSTRUCTION_FIRE_NOW = 1;
public static final int MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT = 2;
public static final int MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT = 3;
public static final int MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT = 4;
public static final int MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT = 5;
样例

MISFIRE_INSTRUCTION_FIRE_NOW

如果repeat count大于0,则MISFIRE_INSTRUCTION_FIRE_NOW等价于MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT

1
2
3
4
5
6
7
8
9
10
11
2018-06-15 18:22:55.522  INFO 6544 --- [eduler_Worker-1] wang.ray.sample.quartz.MyJob             : 1, Hello World!  MyJob is executing.
2018-06-15 18:23:03.763  INFO 6544 --- [eduler_Worker-2] wang.ray.sample.quartz.MyJob             : 2, Hello World!  MyJob is executing.
2018-06-15 18:23:12.555  INFO 6544 --- [eduler_Worker-3] wang.ray.sample.quartz.MyJob             : 3, Hello World!  MyJob is executing.
【4秒多后】
2018-06-15 18:23:16.750  INFO 6544 --- [_MisfireHandler] o.s.s.quartz.LocalDataSourceJobStore     : Handling 1 trigger(s) that missed their scheduled fire-time.
2018-06-15 18:23:25.120  INFO 6544 --- [eduler_Worker-4] wang.ray.sample.quartz.MyJob             : 4, Hello World!  MyJob is executing.
2018-06-15 18:23:33.422  INFO 6544 --- [eduler_Worker-5] wang.ray.sample.quartz.MyJob             : 5, Hello World!  MyJob is executing.
2018-06-15 18:23:41.983  INFO 6544 --- [eduler_Worker-6] wang.ray.sample.quartz.MyJob             : 6, Hello World!  MyJob is executing.
【4秒多后】
2018-06-15 18:23:46.753  INFO 6544 --- [_MisfireHandler] o.s.s.quartz.LocalDataSourceJobStore     : Handling 1 trigger(s) that missed their scheduled fire-time.
2018-06-15 18:23:55.102  INFO 6544 --- [eduler_Worker-7] wang.ray.sample.quartz.MyJob             : 7, Hello World!  MyJob is executing.

MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2018-06-15 17:11:00.940  INFO 11392 --- [eduler_Worker-1] wang.ray.sample.quartz.MyJob             : 1, Hello World!  MyJob is executing.
2018-06-15 17:11:09.687  INFO 11392 --- [eduler_Worker-2] wang.ray.sample.quartz.MyJob             : 2, Hello World!  MyJob is executing.
2018-06-15 17:11:18.134  INFO 11392 --- [eduler_Worker-3] wang.ray.sample.quartz.MyJob             : 3, Hello World!  MyJob is executing.
【4秒多后】
2018-06-15 17:11:22.153  INFO 11392 --- [_MisfireHandler] o.s.s.quartz.LocalDataSourceJobStore     : Handling 1 trigger(s) that missed their scheduled fire-time.
2018-06-15 17:11:30.369  INFO 11392 --- [eduler_Worker-4] wang.ray.sample.quartz.MyJob             : 4, Hello World!  MyJob is executing.
2018-06-15 17:11:38.659  INFO 11392 --- [eduler_Worker-5] wang.ray.sample.quartz.MyJob             : 5, Hello World!  MyJob is executing.
2018-06-15 17:11:47.217  INFO 11392 --- [eduler_Worker-6] wang.ray.sample.quartz.MyJob             : 6, Hello World!  MyJob is executing.
【4秒多后】
2018-06-15 17:11:52.152  INFO 11392 --- [_MisfireHandler] o.s.s.quartz.LocalDataSourceJobStore     : Handling 1 trigger(s) that missed their scheduled fire-time.
2018-06-15 17:12:00.561  INFO 11392 --- [eduler_Worker-7] wang.ray.sample.quartz.MyJob             : 7, Hello World!  MyJob is executing.
2018-06-15 17:12:08.785  INFO 11392 --- [eduler_Worker-8] wang.ray.sample.quartz.MyJob             : 8, Hello World!  MyJob is executing.
2018-06-15 17:12:17.167  INFO 11392 --- [eduler_Worker-9] wang.ray.sample.quartz.MyJob             : 9, Hello World!  MyJob is executing.
【4秒多后】
2018-06-15 17:12:22.153  INFO 11392 --- [_MisfireHandler] o.s.s.quartz.LocalDataSourceJobStore     : Handling 1 trigger(s) that missed their scheduled fire-time.
2018-06-15 17:12:30.676  INFO 11392 --- [duler_Worker-10] wang.ray.sample.quartz.MyJob             : 10, Hello World!  MyJob is executing.
2018-06-15 17:12:39.022  INFO 11392 --- [eduler_Worker-1] wang.ray.sample.quartz.MyJob             : 11, Hello World!  MyJob is executing.

MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT

1
2
3
4
5
6
7
8
9
10
11
2018-06-15 17:13:57.502  INFO 11520 --- [eduler_Worker-1] wang.ray.sample.quartz.MyJob             : 1, Hello World!  MyJob is executing.
2018-06-15 17:14:05.736  INFO 11520 --- [eduler_Worker-2] wang.ray.sample.quartz.MyJob             : 2, Hello World!  MyJob is executing.
2018-06-15 17:14:14.138  INFO 11520 --- [eduler_Worker-3] wang.ray.sample.quartz.MyJob             : 3, Hello World!  MyJob is executing.
【4秒多后】
2018-06-15 17:14:18.490  INFO 11520 --- [_MisfireHandler] o.s.s.quartz.LocalDataSourceJobStore     : Handling 1 trigger(s) that missed their scheduled fire-time.
2018-06-15 17:14:26.695  INFO 11520 --- [eduler_Worker-4] wang.ray.sample.quartz.MyJob             : 4, Hello World!  MyJob is executing.
2018-06-15 17:14:35.175  INFO 11520 --- [eduler_Worker-5] wang.ray.sample.quartz.MyJob             : 5, Hello World!  MyJob is executing.
2018-06-15 17:14:43.578  INFO 11520 --- [eduler_Worker-6] wang.ray.sample.quartz.MyJob             : 6, Hello World!  MyJob is executing.
【4秒多后】
2018-06-15 17:14:48.489  INFO 11520 --- [_MisfireHandler] o.s.s.quartz.LocalDataSourceJobStore     : Handling 1 trigger(s) that missed their scheduled fire-time.
2018-06-15 17:14:56.988  INFO 11520 --- [eduler_Worker-7] wang.ray.sample.quartz.MyJob             : 7, Hello World!  MyJob is executing.

MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT

1
2
3
4
5
6
7
8
9
10
11
2018-06-15 17:30:59.015  INFO 4461 --- [eduler_Worker-1] wang.ray.sample.quartz.MyJob             : 1, Hello World!  MyJob is executing.
2018-06-15 17:31:07.278  INFO 4461 --- [eduler_Worker-2] wang.ray.sample.quartz.MyJob             : 2, Hello World!  MyJob is executing.
2018-06-15 17:31:15.671  INFO 4461 --- [eduler_Worker-3] wang.ray.sample.quartz.MyJob             : 3, Hello World!  MyJob is executing.
【随后】
2018-06-15 17:31:20.035  INFO 4461 --- [_MisfireHandler] o.s.s.quartz.LocalDataSourceJobStore     : Handling 1 trigger(s) that missed their scheduled fire-time.
2018-06-15 17:31:28.354  INFO 4461 --- [eduler_Worker-4] wang.ray.sample.quartz.MyJob             : 4, Hello World!  MyJob is executing.
2018-06-15 17:31:36.587  INFO 4461 --- [eduler_Worker-5] wang.ray.sample.quartz.MyJob             : 5, Hello World!  MyJob is executing.
2018-06-15 17:31:45.001  INFO 4461 --- [eduler_Worker-6] wang.ray.sample.quartz.MyJob             : 6, Hello World!  MyJob is executing.
2018-06-15 17:31:53.268  INFO 4461 --- [eduler_Worker-7] wang.ray.sample.quartz.MyJob             : 7, Hello World!  MyJob is executing.
【随后】
2018-06-15 17:32:00.034  INFO 4461 --- [_MisfireHandler] o.s.s.quartz.LocalDataSourceJobStore     : Handling 1 trigger(s) that missed their scheduled fire-time.

MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT

1
2
3
4
5
6
7
8
9
10
2018-06-15 17:34:37.562  INFO 4600 --- [eduler_Worker-1] wang.ray.sample.quartz.MyJob             : 1, Hello World!  MyJob is executing.
2018-06-15 17:34:45.963  INFO 4600 --- [eduler_Worker-2] wang.ray.sample.quartz.MyJob             : 2, Hello World!  MyJob is executing.
2018-06-15 17:34:54.210  INFO 4600 --- [eduler_Worker-3] wang.ray.sample.quartz.MyJob             : 3, Hello World!  MyJob is executing.
【随后】
2018-06-15 17:34:58.679  INFO 4600 --- [_MisfireHandler] o.s.s.quartz.LocalDataSourceJobStore     : Handling 1 trigger(s) that missed their scheduled fire-time.
2018-06-15 17:35:06.924  INFO 4600 --- [eduler_Worker-4] wang.ray.sample.quartz.MyJob             : 4, Hello World!  MyJob is executing.
2018-06-15 17:35:15.647  INFO 4600 --- [eduler_Worker-5] wang.ray.sample.quartz.MyJob             : 5, Hello World!  MyJob is executing.
2018-06-15 17:35:24.997  INFO 4600 --- [eduler_Worker-6] wang.ray.sample.quartz.MyJob             : 6, Hello World!  MyJob is executing.
【随后】
2018-06-15 17:35:28.679  INFO 4600 --- [_MisfireHandler] o.s.s.quartz.LocalDataSourceJobStore     : Handling 1 trigger(s) that missed their scheduled fire-time.

CronTrigger

1
2
1
2
1
2
public static final int MISFIRE_INSTRUCTION_FIRE_ONCE_NOW = 1;
public static final int MISFIRE_INSTRUCTION_DO_NOTHING = 2;

DailyTimeIntervalTrigger

1
2
1
2
1
2
public static final int MISFIRE_INSTRUCTION_FIRE_ONCE_NOW = 1;
public static final int MISFIRE_INSTRUCTION_DO_NOTHING = 2;

CalendarIntervalTrigger

1
2
1
2
1
2
public static final int MISFIRE_INSTRUCTION_FIRE_ONCE_NOW = 1;
public static final int MISFIRE_INSTRUCTION_DO_NOTHING = 2;

spring

SchedulerFactoryBean

创建和配置Quartz Scheduler,管理其生命周期作为Spring上下文的一部分,暴漏Quartz Scheduler为bean用做依赖注入。

允许注入JobDetails,Calendars和Triggers,自动启动和停止Quartz Scheduler,一般情况下不需要直接使用Quartz Scheduler。动态创建任务场景才需要直接使用Quartz Scheduler。

datasource

JobDetailFactoryBean

SimpleTriggerFactoryBean

CronTriggerFactoryBean

MethodInvokingJobDetailFactoryBean

JobDetail和Job的一种适配,将POJO适配为Job

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- POJO,不需要集成Job接口-->
<bean id="quartzJob" class="com.kay.quartz.QuartzJob"></bean>

<bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
	<!-- 调用的类 -->
	<property name="targetObject">
		<ref bean="quartzJob"/>
	</property>
	<!-- 调用类中的方法 -->
	<property name="targetMethod">
		<value>work</value>
	</property>
</bean>

AdaptableJobFactory

Job工厂的一种适配,可以支持Job和Runnable

DelegatingJob

Job的一种适配,将Runnable适配为Job

QuartzJobBean和SpringBeanJobFactory

Job的一种适配,将POJO适配为Job,同时将SchedulerContext和JobDataMap中的属性自动注入到POJO

LocalDataSourceJobStore

基于Spring管理的数据源的JobStoreCMT实现

LocalTaskExecutorThreadPool

基于Spring管理的Executor的ThreadPool实现

spring-boot-starter-quartz

QuartzAutoConfiguration

会自动注入JobDetail、Trigger、Calendar、QuartzProperties和SchedulerFactoryBeanCustomizer的实例bean。

使用Quartz原生数据源

1
2
3
4
5
6
7
8
9
10
11
12
13
#quartz
spring.quartz.properties.org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
spring.quartz.properties.org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
spring.quartz.properties.org.quartz.jobStore.useProperties=false
spring.quartz.properties.org.quartz.jobStore.dataSource=myDS
spring.quartz.properties.org.quartz.jobStore.tablePrefix=QRTZ_
#quartz datasource
spring.quartz.properties.org.quartz.dataSource.myDS.driver=com.mysql.jdbc.Driver
spring.quartz.properties.org.quartz.dataSource.myDS.URL=jdbc:mysql://localhost/quartz
spring.quartz.properties.org.quartz.dataSource.myDS.user=root
spring.quartz.properties.org.quartz.dataSource.myDS.password=mysql
spring.quartz.properties.org.quartz.dataSource.myDS.maxConnections=5
spring.quartz.properties.org.quartz.dataSource.myDS.validationQuery=select 0

使用自定义数据源(需要DataSource实现和spring-jdbc)

1
2
3
4
5
6
7
8
# 是否执行初始化SQL脚本
spring.quartz.jdbc.initialize-schema=never
# 内存还是jdbc JobStore
spring.quartz.job-store-type=jdbc

spring.datasource.url=jdbc:mysql://localhost/quartz
spring.datasource.username=root
spring.datasource.password=mysql
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 创建的Trigger会自动注入到Scheduler
@Bean
    public SimpleTriggerFactoryBean simpleTriggerFactoryBean(){
        SimpleTriggerFactoryBean simpleTriggerFactoryBean = new SimpleTriggerFactoryBean();
        simpleTriggerFactoryBean.setName("trigger5");
        simpleTriggerFactoryBean.setGroup("group1");

        JobDetail job = newJob(MyJob.class)
                .withIdentity("job11", "group1")
                .usingJobData("key0", "value1")
                .usingJobData("key1", "value1")
                .build();

        simpleTriggerFactoryBean.setJobDetail(job);

        simpleTriggerFactoryBean.setRepeatCount(3);
        simpleTriggerFactoryBean.setRepeatInterval(3*1000);
        return simpleTriggerFactoryBean;
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 创建的Trigger会自动注入到Scheduler
@Bean
    public CronTriggerFactoryBean cronTriggerFactoryBean() {
        CronTriggerFactoryBean cronTriggerFactoryBean = new CronTriggerFactoryBean();
        cronTriggerFactoryBean.setName("trigger8");
        cronTriggerFactoryBean.setGroup("group1");
        cronTriggerFactoryBean.setCronExpression("0/10 * * * * ?");

        JobDetail job = newJob(MyJob.class)
                .withIdentity("job11", "group1")
                .usingJobData("key0", "value1")
                .usingJobData("key1", "value1")
                .build();

        cronTriggerFactoryBean.setJobDetail(job);

        return cronTriggerFactoryBean;
    }