http://www.quartz-scheduler.o…
1. 应用 Quartz
-
Quartz Scheduler 一旦敞开,无奈重启
- 须要从新实例化
- 提供了暂停状态
SchedulerFactory schedFact = new org.quartz.impl.StdSchedulerFactory();
Scheduler sched = schedFact.getScheduler();
sched.start();
// define the job and tie it to our HelloJob class
JobDetail job = newJob(HelloJob.class)
.withIdentity("myJob", "group1")
.build();
// Trigger the job to run now, and then every 40 seconds
Trigger trigger = newTrigger()
.withIdentity("myTrigger", "group1")
.startNow()
.withSchedule(simpleSchedule()
.withIntervalInSeconds(40)
.repeatForever())
.build();
// Tell quartz to schedule the job using our trigger
sched.scheduleJob(job, trigger);
2.Quartz API,JOB 和 Trigger
http://www.quartz-scheduler.o…
- JobBuilder- 用于定义 / 构建 JobDetail 实例,该实例定义 Jobs 实例。
- JobDetail- 用于定义 Job 的实例。
- Job- 由您心愿由调度程序执行的组件实现的接口。
- TriggerBuilder- 用于定义 / 构建触发器实例。
- Trigger- 定义将在其上执行给定作业的时间表的组件。
Scheduler
一个 Scheduler
的生命周期是由它的创作范畴,通过 SchedulerFactory
来调用他的创立和敞开。
创立 Scheduler
后,就能够应用它
- 增加
- 删除
- 列出 Job 和 Trigger
以及执行其余与打算相干的操作(例如暂停触发器)。然而,调度程序在应用 starter
之前实际上不会对任何 trigger 执行工作起作用
Builder 的演示
// define the job and tie it to our HelloJob class
JobDetail job = newJob(HelloJob.class)
.withIdentity("myJob", "group1") // name "myJob", group "group1"
.build();
// Trigger the job to run now, and then every 40 seconds
Trigger trigger = newTrigger()
.withIdentity("myTrigger", "group1")
.startNow()
.withSchedule(simpleSchedule()
.withIntervalInSeconds(40)
.repeatForever())
.build();
// Tell quartz to schedule the job using our trigger
sched.scheduleJob(job, trigger);
Jobs and Triggers
Job 是一个接口
public interface Job {public void execute(JobExecutionContext context)
throws JobExecutionException;
}
当一个 Jobs 被触发执行的时候,会调用 execute
, 在一个 Scheduler 的worker
的线程外面。
JobExecutionContext
提供了运行的时候须要的一些变量
JobDetail
在 Job
加到 Scheduler 的时候,他能蕴含一些属性用来设置给 job 的,比如说JobDataMap
, 他能让咱们存储一些状态信息给咱们 Job
Trigger
用来触发执行工作,也蕴含了 JobDataMap, 他能通知咱们什么时候执行工作。默认有 SimpleTrigger
和CronTrigger
SimpleTriggere
如果您须要一次性执行(在给定时刻只执行一个作业),或者如果您须要在给定工夫触发一个作业,并让它反复 N 次,两次执行之间的提早为 T,那么 SimpleTrigger 十分不便。
如果您心愿基于相似日历的日程安排 (如每个周五中午或每个月的第 10 天的 10:15) 进行触发,那么 CronTrigger 十分有用。
Identifies
JobKey and TriggerKey
groups
3.Job 和 JobDetail 的详情
// define the job and tie it to our HelloJob class
JobDetail job = newJob(HelloJob.class)
.withIdentity("myJob", "group1") // name "myJob", group "group1"
.build();
// Trigger the job to run now, and then every 40 seconds
Trigger trigger = newTrigger()
.withIdentity("myTrigger", "group1")
.startNow()
.withSchedule(simpleSchedule()
.withIntervalInSeconds(40)
.repeatForever())
.build();
// Tell quartz to schedule the job using our trigger
sched.scheduleJob(job, trigger);
public class HelloJob implements Job {public HelloJob() { }
public void execute(JobExecutionContext context)
throws JobExecutionException
{System.err.println("Hello! HelloJob is executing.");
}
}
每次执行的时候,都会在调用 execute 前创立其实例,执行实现后会删除和垃圾回收。
- 要求 Job 实现类必须有无参结构
-
Job 下面定义的状态数据字段没有什么用,因为每次都是新的,而且会被革除
- 这种存储信息最好应用
JobDataMap
- 这种存储信息最好应用
JobDataMap
// define the job and tie it to our DumbJob class
JobDetail job = newJob(DumbJob.class)
.withIdentity("myJob", "group1") // name "myJob", group "group1"
.usingJobData("jobSays", "Hello World!")
.usingJobData("myFloatValue", 3.141f)
.build();
public class DumbJob implements Job {public DumbJob() { }
public void execute(JobExecutionContext context)
throws JobExecutionException
{JobKey key = context.getJobDetail().getKey();
JobDataMap dataMap = context.getJobDetail().getJobDataMap();
String jobSays = dataMap.getString("jobSays");
float myFloatValue = dataMap.getFloat("myFloatValue");
System.err.println("Instance" + key + "of DumbJob says:" + jobSays + ", and val is:" + myFloatValue);
}
}
- 存储的对象会被序列化,须要留神版本问题
- 如果 keyName 对应对应的
set
+Keyname 那么会调用对应的 Job 的 setter 办法
JobExecutionContext
可能帮咱们合并 Trigger
和JobDetail
上的 JobDataMap
外面的值,Trigger 上的 key
会笼罩 JobDetail
上的
public class DumbJob implements Job {public DumbJob() { }
public void execute(JobExecutionContext context)
throws JobExecutionException
{JobKey key = context.getJobDetail().getKey();
JobDataMap dataMap = context.getMergedJobDataMap(); // Note the difference from the previous example
String jobSays = dataMap.getString("jobSays");
float myFloatValue = dataMap.getFloat("myFloatValue");
ArrayList state = (ArrayList)dataMap.get("myStateData");
state.add(new Date());
System.err.println("Instance" + key + "of DumbJob says:" + jobSays + ", and val is:" + myFloatValue);
}
}
Job 实例
触发器触发,会加载对应的 JobDetail, 并且通过配置的 Jobfactory
来实例化对应的Job
并且尝试在对应的 JobFactory
上调用与 JobDataMap 中的键名匹配的 setter 办法。
工作状态和并发执行
@DisallowConcurrentExecution
这个用来加到 Job
类下面,用来通知 Quartz 不要同时执行同一个 Job
@PersistJobDataAfterExecution
也是加到 Job
类下面,通知 Quartz 执行实现后 (没有出现异常),更新JobDataMap
的存储,用来通知下次执行的时候可能获取以后设置的值。
其余
-
持久性
- 如果 Job 不会再应用了,会主动从
Scheduler
删除,这个和Trigger
相干
- 如果 Job 不会再应用了,会主动从
RequestsRecovery – if a job“requests recovery”, and it is executing during the time of a‘hard shutdown’of the scheduler (i.e. the process it is running within crashes, or the machine is shut off), then it is re-executed when the scheduler is started again. In this case, the JobExecutionContext.isRecovering() method will return true.
4. 对于 Trigger
像 Job 一样,Trigger 也很容易应用,然而的确蕴含各种自定义选项,在充分利用 Quartz 之前,您须要理解它们并理解它们。同样,如前所述,您能够抉择不同类型的 Trigger 来满足不同的调度需要。
通用的 Trigger 属性
-
JobKey
:- 触发器执行的时候执行的 job 标识
startTime
: 标识触发器失效工夫endTime
: 触发器完结工夫
优先级
当咱们触发器很多,Quartz 线程池的工作线程很少的时候,可能没有足够的资源共事启动所有要触发的触发器,如果没有指定优先级,会应用默认优先级5
优先级配置反对任何整数值 负数或者正数
留神:只有触发工夫雷同的时候才会比拟优先级,打算在 10:59 触发的触发器将始终在打算在 11:00 触发的触发器之前触发。
留神:当检测到触发器的作业须要复原时,其复原的排定的优先级与原始触发器雷同。
CronTrigger trigger = TriggerBuilder.newTrigger().
withIdentity(getTriggerKey(scheduleJobEntity.getId())).
withSchedule(cronScheduleBuilder).
startAt(scheduleJobEntity.getStartTime()).
endAt(scheduleJobEntity.getEndTime()).
withPriority(10).
build();
日历
日历对于从触发器的触发打算中排除时间段很有用。例如,您能够创立一个触发器,该触发器在每个工作日的上午 9:30 触发一个工作,而后增加一个排除所有企业假期的日历。
public interface Calendar {public boolean isTimeIncluded(long timeStamp);
public long getNextIncludedTime(long timeStamp);
}
上述接口都是毫秒,为了不便 Quartz 还提供了 HolidayCalendar
来提供终日的排除
HolidayCalendar cal = new HolidayCalendar();
cal.addExcludedDate(someDate);
cal.addExcludedDate(someOtherDate);
sched.addCalendar("myHolidays", cal, false);
Trigger t = newTrigger()
.withIdentity("myTrigger")
.forJob("myJob")
.withSchedule(dailyAtHourAndMinute(9, 30)) // execute job daily at 9:30
.modifiedByCalendar("myHolidays") // but not on holidays
.build();
// .. schedule job with trigger
Trigger t2 = newTrigger()
.withIdentity("myTrigger2")
.forJob("myJob2")
.withSchedule(dailyAtHourAndMinute(11, 30)) // execute job daily at 11:30
.modifiedByCalendar("myHolidays") // but not on holidays
.build();
// .. schedule job with trigger2
5.SimpleTrigger
如果您须要在特定的工夫或特定的工夫精确执行一次作业,而后在特定的工夫距离反复执行一次,SimpleTrigger应该能够满足您的调度需要。例如,如果您想让触发器在 2015 年 1 月 13 日上午 11:23:54 触发,或者您想在那时触发,而后每十秒钟再触发五次。
-
属性
- 开始工夫和完结工夫
-
反复计数
- 能够为零, 正整数,常量值
SimpleTrigger.REPEAT_INDEFINITELY
- 能够为零, 正整数,常量值
-
反复距离
- 必须为零或者正值,代表为毫秒数
- 距离为 0
DateBuilder
类有助于计算触发器的触发工夫,取决于咱们的开始和完结工夫
开始和完结工夫优先级大于反复计数
为特定工夫建设触发器,不要反复
import static org.quartz.TriggerBuilder.*;
import static org.quartz.SimpleScheduleBuilder.*;
import static org.quartz.DateBuilder.*:
SimpleTrigger trigger = (SimpleTrigger) newTrigger()
.withIdentity("trigger1", "group1")
.startAt(myStartTime) // some Date
.forJob("job1", "group1") // identify job with name, group strings
.build();
为特定工夫建设触发器,每十秒反复十次
trigger = newTrigger()
.withIdentity("trigger3", "group1")
.startAt(myTimeToStartFiring) // if a start time is not given (if this line were omitted), "now" is implied
.withSchedule(simpleSchedule()
.withIntervalInSeconds(10)
.withRepeatCount(10)) // note that 10 repeats will give a total of 11 firings
.forJob(myJob) // identify job with handle to its JobDetail itself
.build();
建设一个触发器,该触发器将在将来五分钟触发一次
trigger = (SimpleTrigger) newTrigger()
.withIdentity("trigger5", "group1")
.startAt(futureDate(5, IntervalUnit.MINUTE)) // use DateBuilder to create a date in the future
.forJob(myJobKey) // identify job with its JobKey
.build();
建设一个当初将触发的触发器,而后每五分钟反复一次,直到 22:00
trigger = newTrigger()
.withIdentity("trigger7", "group1")
.withSchedule(simpleSchedule()
.withIntervalInMinutes(5)
.repeatForever())
.endAt(dateOf(22, 0, 0))
.build();
建设一个触发器,该触发器将在下一个小时的顶部触发,而后永远每 2 小时反复一次:
trigger = newTrigger()
.withIdentity("trigger8") // because group is not specified, "trigger8" will be in the default group
.startAt(evenHourDate(null)) // get the next even-hour (minutes and seconds zero ("00:00"))
.withSchedule(simpleSchedule()
.withIntervalInHours(2)
.repeatForever())
// note that in this example, 'forJob(..)' is not called
// - which is valid if the trigger is passed to the scheduler along with the job
.build();
scheduler.scheduleJob(trigger, job);
SimpleTrigger MisFire
MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY
MISFIRE_INSTRUCTION_FIRE_NOW
MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT
MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT
MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT
MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT
6.CronTrigger
如果您须要一个基于相似于日历的概念而不是依据 SimpleTrigger 的确切距离反复产生的工作辞退时间表,则 CronTrigger 通常比 SimpleTrigger 有用。
应用 CronTrigger,您能够指定触发打算,例如“每个星期五中午”,“每个工作日至 9:30”,甚至“每个星期一,星期三的上午 9:00 至 10:00 之间的每 5 分钟”和一月的星期五”。
即便如此,与 SimpleTrigger 一样,CronTrigger 具备一个startTime
,用于指定打算何时失效;以及一个(可选的)endTime
,用于指定打算何时终止。
Cron 表达式
http://www.quartz-scheduler.o…
表达式用于配置 CronTrigger 的实例。Cron-Expression 是实际上由七个子表达式组成的字符串,它们形容了日程表的各个细节。这些子表达式用空格分隔,代表:
- 秒
- 分钟
- 小时
- 月的某一天
- 月
- 星期几
- 年(可选字段)
-
残缺的 cron 表达式的示例是字符串
0 0 12?* WED
- 代表每个星期三的 12:00:00 pm”。
各个子表达式能够蕴含范畴和 / 或列表。
- 例如,上一个
WED
字段能够替换为“MON-FRI”,“MON,WED,FRI”,甚至“MON-WED,SAT”。
通配符 ''
能够用于示意字段的所有可能值
*
代表每一周的每一天
/
代表指定值的增量。
- 例如在分钟字段
0/15
,示意每个小时的 15 分钟,从 0 分钟开始, - 如果用
3/20
示意每小时的第 20 分钟,从地三分钟开始 - <span style=’color:red’> 留神 </span>:
/35
并不代表每 35 分钟,而是每小时的每 35 分钟,从零分钟开始即0/35
?
: 星期几和星期几字段容许应用字符。用于指定“无特定值”。当您须要在两个字段之一中指定某项而不是另一个字段时,这很有用
L
: 用于月和周,示意最初一个,月示意月的最初一天 1 月 31 日,2 月 28 日。
用于星期的话示意 7
或者 sat
(周六),如果6L
示意该月的最初一个最初一个星期五,能够用来指定该月最初一天的偏移量L-3
,示意日历月的倒数第三天
W
用来指定给定日期的工作日 (周一到星期五),15W
指的是离每月 15 日最近的工作日
#
用于指定每月的第“n”个 XXX 工作日。例如,星期几
字段中的 6#3
或 FRI#3
的值示意 每月的第三个星期五
。
Cron 表达式示例
CronTrigger 示例 1 - 用于创立仅每 5 分钟触发一次的触发器的表达式
0 0/5 * * *?
CronTrigger 示例 2 - 创立一个触发器的表达式,该触发器每 5 分钟触发一次,每分钟后 10 秒(例如 10:00:10 am,10:05:10 am 等)触发。
10 0/5 * * *?
CronTrigger 示例 3 - 创立一个触发器的表达式,该触发器在每个星期三和星期五的 10:30、11:30、12:30 和 13:30 触发。
0 30 10-13?* WED,FRI
CronTrigger 示例 4 - 创立一个触发器的表达式,该触发器在每月的 5 号和 20 号的上午 8 点到 10 点之间每半小时触发一次。请留神,触发器不会在上午 10:00,仅在 8:00、8:30、9:00 和 9:30 触发
0 0/30 8-9 5,20 * ?
请留神,某些打算要求太过简单而无奈用一次触发来表白,例如“上午 9:00 至上午 10:00 之间每 5 分钟一次,下午 1:00 至 10:00 下午每 20 分钟一次”。这种状况下的解决方案是简略地创立两个触发器,并注册两个触发器以运行雷同的作业。
构建 CronTriggers
import static org.quartz.TriggerBuilder.*;
import static org.quartz.CronScheduleBuilder.*;
import static org.quartz.DateBuilder.*:
建设一个触发器,该触发器每天每天从早上 8 点到下午 5 点之间每隔一分钟触发一次:
trigger = newTrigger()
.withIdentity("trigger3", "group1")
.withSchedule(cronSchedule("0 0/2 8-17 * * ?"))
.forJob("myJob", "group1")
.build();
建设触发器,每天 10:42 am 触发
trigger = newTrigger()
.withIdentity("trigger3", "group1")
.withSchedule(dailyAtHourAndMinute(10, 42))
.forJob(myJobKey)
.build();
或者
trigger = newTrigger()
.withIdentity("trigger3", "group1")
.withSchedule(cronSchedule("0 42 10 * * ?"))
.forJob(myJobKey)
.build();
构建一个触发器,该触发器将在星期三上午 10:42,应用零碎默认值以外的 TimeZone 触发
trigger = newTrigger()
.withIdentity("trigger3", "group1")
.withSchedule(weeklyOnDayAndHourAndMinute(DateBuilder.WEDNESDAY, 10, 42))
.forJob(myJobKey)
.inTimeZone(TimeZone.getTimeZone("America/Los_Angeles"))
.build();
或者
trigger = newTrigger()
.withIdentity("trigger3", "group1")
.withSchedule(cronSchedule("0 42 10 ? * WED"))
.inTimeZone(TimeZone.getTimeZone("America/Los_Angeles"))
.forJob(myJobKey)
.build();
CronTrigger MisFire 阐明
MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY
MISFIRE_INSTRUCTION_DO_NOTHING
MISFIRE_INSTRUCTION_FIRE_NOW
trigger = newTrigger()
.withIdentity("trigger3", "group1")
.withSchedule(cronSchedule("0 0/2 8-17 * * ?")
..withMisfireHandlingInstructionFireAndProceed())
.forJob("myJob", "group1")
.build();
7.TriggerListeners 和 JobListeners
侦听器是您创立的对象,用于依据调度程序中产生的事件执行操作。您可能会猜到,TriggerListeners*接管与触发器相干的事件,而JobListeners* 接管与作业相干的事件。
public interface TriggerListener {public String getName();
public void triggerFired(Trigger trigger, JobExecutionContext context);
public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context);
public void triggerMisfired(Trigger trigger);
public void triggerComplete(Trigger trigger, JobExecutionContext context,
int triggerInstructionCode);
}
与作业相干的事件包含:行将执行作业的告诉,以及作业实现执行时的告诉。
public interface JobListener {public String getName();
public void jobToBeExecuted(JobExecutionContext context);
public void jobExecutionVetoed(JobExecutionContext context);
public void jobWasExecuted(JobExecutionContext context,
JobExecutionException jobException);
}
应用本人的监听器
要创立侦听器,只需创立一个实现 org.quartz.TriggerListener 或 org.quartz.JobListener 接口的对象。而后,在运行时将侦听器注册到调度程序,并且必须给其指定名称(或者说,它们必须通过其 getName()办法公布本人的名称)。
侦听器与调度程序的 ListenerManager 一起注册,该 Matcher 形容了侦听器要为其接管事件的作业 / 触发器。
侦听器在运行时会向调度程序注册,并且不会与作业和触发器一起存储在 JobStore 中。这是因为侦听器通常是您的应用程序的集成点。因而,每次您的利用程序运行时,都须要在调度程序中从新注册侦听器。
scheduler.getListenerManager().addJobListener(myJobListener, jobKeyEquals(jobKey("myJobName", "myJobGroup")));
增加对特定组的所有作业感兴趣的 JobListener
scheduler.getListenerManager().addJobListener(myJobListener, jobGroupEquals("myJobGroup"));
增加对两个特定组的所有作业感兴趣的 JobListener
scheduler.getListenerManager().addJobListener(myJobListener, or(jobGroupEquals("myJobGroup"), jobGroupEquals("yourGroup")));
增加对所有作业感兴趣的 JobListener
scheduler.getListenerManager().addJobListener(myJobListener, allJobs());
Quartz 的大多数用户都不应用侦听器,然而当应用程序需要创立事件告诉的需要时,侦听器十分不便,而 Job 自身不用显式告诉应用程序。
8.SchedulerListeners
*SchedulerListener*与 TriggerListeners 和 JobListeners 十分类似,除了它们在 Scheduler 本身内接管事件的告诉 - 不肯定与特定触发器或作业无关的事件。
与调度程序相干的事件包含:增加作业 / 触发器,移除作业 / 触发器,调度程序内的严重错误,告诉调度程序正在敞开等。
public interface SchedulerListener {public void jobScheduled(Trigger trigger);
public void jobUnscheduled(String triggerName, String triggerGroup);
public void triggerFinalized(Trigger trigger);
public void triggersPaused(String triggerName, String triggerGroup);
public void triggersResumed(String triggerName, String triggerGroup);
public void jobsPaused(String jobName, String jobGroup);
public void jobsResumed(String jobName, String jobGroup);
public void schedulerError(String msg, SchedulerException cause);
public void schedulerStarted();
public void schedulerInStandbyMode();
public void schedulerShutdown();
public void schedulingDataCleared();}
增加一个 SchedulerListener
scheduler.getListenerManager().addSchedulerListener(mySchedListener);
删除 SchedulerListener
scheduler.getListenerManager().removeSchedulerListener(mySchedListener);
9.JobStore
JobStore 负责跟踪您提供给调度程序的所有“JobData”:作业,触发器,日历等。为 Quartz 调度程序实例抉择适当的 JobStore 是重要的一步。
侥幸的是,一旦您理解了两者之间的差别,那么抉择就非常容易。
您在提供给用于生成调度程序实例的 SchedulerFactory 的属性文件(或对象)中申明调度程序应应用哪个 JobStore(及其配置设置)。
切勿在代码中间接应用 JobStore 实例。因为某些起因,许多人试图这样做。JobStore 用于 Quartz 自身的幕后应用。您必须(通过配置)通知 Quartz 应用哪个 JobStore,然而您仅应在代码中应用 Scheduler 接口。
RAMJobStore
RAMJobStore 是应用最简略的 JobStore,也是性能最高的(就 CPU 工夫而言)。RAMJobStore 的名称很显著:
- 将所有数据保留在 RAM 中。这就是为什么它闪电般的疾速,也是为什么它是如此简略的配置。
-
毛病是
- 当您的应用程序完结(或解体)时,所有调度信息都将失落 -
- 这意味着 RAMJobStore 无奈承受
Job
和Trigger
上的duriable
设置。 - 对于某些应用程序,这是能够承受的,甚至是所需的行为,然而对于其余应用程序,这可能是灾难性的。
要应用 RAMJobStore(并假如您正在应用 StdSchedulerFactory),只需将类名称 org.quartz.simpl.RAMJobStore 指定为用于配置石英的 JobStore 类属性:
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
JDBCJobStore
JDBCJobStore 也被失当地命名 - 它通过 JDBC 将其所有数据保留在数据库中。因而,它的配置要比 RAMJobStore 简单一些,并且速度也没有那么快。
然而,性能降落并不是很蹩脚,尤其是当您应用主键上的索引构建数据库表时。在具备适当 LAN(在调度程序和数据库之间)的相当古代的一组计算机上,检索和更新触发触发器的工夫通常 少于 10 毫秒
。
JDBCJobStore 简直能够与任何数据库一起应用,它已被 Oracle,PostgreSQL,MySQL,MS SQLServer,HSQLDB 和 DB2 宽泛应用。要应用 JDBCJobStore,必须首先创立一组数据库表供 Quartz 应用。您能够在 Quartz 发行版的 docs / dbTables
目录中找到表创立 SQL 脚本。如果没有针对您的数据库类型的脚本,只需查看现有脚本之一,而后以数据库所需的任何形式对其进行批改。须要留神的一件事是,在这些脚本中,所有表都以前缀“QRTZ_”结尾(例如表“QRTZ_TRIGGERS”和“QRTZ_JOB_DETAIL”)。只有您告知 JDBCJobStore 前缀是什么(在 Quartz 属性中),该前缀实际上就能够是您想要的任何前缀。应用不同的前缀对于创立多个表集,多个调度程序实例可能很有用,
创立表之后,在配置和启动 JDBCJobStore 之前,您须要做出另一个重要决定。您须要确定您的应用程序须要哪种事物来治理。如果您不须要将调度命令(例如增加和删除触发器)与其余事务绑定,则能够让 Quartz 通过将 JobStoreTX
用作 JobStore 来治理事务(这是最常见的抉择)。
如果您须要 Quartz 与其余事务一起工作(即在 J2EE 应用程序服务器中),则应应用 JobStoreCMT-
在这种状况下,Quartz 将容许应用程序服务器容器治理事务。
最初一个难题是设置一个 数据源
,JDBCJobStore 能够从该数据源取得与您的数据库的连贯。
数据源是应用几种不同办法之一在 Quartz 属性中定义的。
- 一种办法是让 Quartz 通过提供数据库的所有连贯信息来创立和治理 DataSource 自身。
- 另一种办法是通过提供 JDBCJobStore 数据源的 JNDI 名称,使 Quartz 应用由 Quartz 在其中运行的应用程序服务器治理的数据源。无关属性的详细信息,请查阅
docs / config
文件夹中的示例配置文件。
要应用 JDBCJobStore(并假如您应用的是 StdSchedulerFactory),首先须要将 Quartz 配置的 JobStore 类属性设置为 org.quartz.impl.jdbcjobstore.JobStoreTX
或org.quartz.impl.jdbcjobstore.JobStoreCMT
依据以上几段中的阐明进行的抉择。
配置 Quartz 以应用 JobStoreTx
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
接下来,您须要抉择一个 DriverDelegate 供 JobStore 应用。DriverDelegate 负责实现特定数据库可能须要的任何 JDBC 工作。
StdJDBCDelegate 是应用“原始”JDBC 代码(和 SQL 语句)实现其工作的委托。如果没有专门为您的数据库创立的另一个委托,请尝试应用此委托 - 咱们仅对应用 StdJDBCDelegate 与(发现最多!)发现问题的数据库进行了特定于数据库的委托。其余代表能够在“org.quartz.impl.jdbcjobstore”包或其子包中找到。其余代表包含 DB2v6Delegate(用于 DB2 版本 6 和更早版本),HSQLDBDelegate(用于 HSQLDB),MSSQLDelegate(用于 Microsoft SQLServer),PostgreSQLDelegate(用于 PostgreSQL),WeblogicDelegate(用于应用由 Weblogic 制作的 JDBC 驱动程序),
抉择委托后,将其类名称设置为 JDBCJobStore 应用的委托。
配置 JDBCJobStore 以应用 DriverDelegate
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
接下来,您须要告诉 JobStore 您正在应用的表前缀(下面已探讨过)。
应用表前缀配置 JDBCJobStore
org.quartz.jobStore.tablePrefix = QRTZ_
最初,您须要设置 JobStore 应该应用哪个数据源。还必须在 Quartz 属性中定义命名的 DataSource。在这种状况下,咱们指定 Quartz 应该应用数据源名称“myDS”(在配置属性的其余地位定义)。
应用要应用的数据源的名称配置 JDBCJobStore
org.quartz.jobStore.dataSource = myDS
如果您的调度程序很忙(即简直总是执行与线程池大小雷同的作业数),那么您可能应该将 DataSource 中的连接数设置为线程池大小的 + 2。
能够将“org.quartz.jobStore.useProperty”配置参数设置为“true”(默认为 false),以批示 JDBCJobStore JobDataMaps 中的所有值均为字符串,因而能够存储为名称 - 值对,而不是而不是将更简单的对象以其序列化模式存储在 BLOB 列中。从久远来看,这样做更加平安,因为能够防止将非 String 类序列化为 BLOB 时呈现的类版本控制问题。
TerracottaJobStore
TerracottaJobStore 提供了一种无需应用数据库即可进行缩放和加强性能的办法。这意味着您的数据库能够免于 Quartz 的负载,而能够为应用程序的其余部分保留所有资源。
TerracottaJobStore 能够集群化或非集群化运行,并且在两种状况下都能够为您的作业数据提供一种存储介质,该存储介质在应用程序重新启动之间是长久的,因为数据存储在 Terracotta 服务器中。它的性能比通过 JDBCJobStore 应用数据库要好得多(大概好一个数量级),但比 RAMJobStore 慢得多。
要应用 TerracottaJobStore(并假如您应用的是 StdSchedulerFactory),只需指定类名称 org.quartz.jobStore.class = org.terracotta.quartz.TerracottaJobStore
作为用于配置 Quartz 的 JobStore 类属性,而后增加一行配置以指定 Terracotta 服务器的地位:
org.quartz.jobStore.class = org.terracotta.quartz.TerracottaJobStore
org.quartz.jobStore.tcConfigUrl = localhost:9510
无关此 JobStore 和 Terracotta 的更多信息,请拜访 http://www.terracotta.org/quartz。
10. 配置,资源应用状况和 SchedulerFactory
Quartz 的体系结构是模块化的,因而要使其运行,须要将多个组件“绑定”在一起。侥幸的是,存在一些帮忙实现此指标的助手。
Quartz 进行工作之前须要配置的次要组件是:
- 线程池
- 作业库
- 数据源
- 调度程序自身
该 * 线程池 * 提供了一组线程供 Quartz 在执行 Jobs 时应用。池中的线程越多,能够并行运行的作业数越多。然而,太多线程可能会使您的零碎瘫痪。
大多数 Quartz 用户发现 5 个左右的线程是足够的 - 因为在任何给定的工夫它们少于 100 个作业,通常不安顿这些作业同时运行,并且这些作业是短暂的(疾速实现)。
其余用户发现他们须要 10、15、50 甚至 100 个线程 - 因为它们具备成千上万个具备各种打算的触发器 - 最终均匀有 10 到 100 个试图在任何给定时刻执行的作业。
为调度程序池找到适合的大小齐全取决于您应用调度程序的目标。没有真正的规定,除了使线程数尽可能小(为了节俭计算机资源)外 - 还要确保有足够的空间按时启动作业。
请留神,如果触发器的触发工夫到了,并且没有可用的线程,Quartz 将阻塞(暂停)直到某个线程可用,而后作业将执行 - 比原先的工夫晚了几毫秒。
如果在调度程序配置的 misfire 阈值
期间没有可用的线程,这甚至可能导致线程不触发。
在 org.quartz.spi 包中定义了 ThreadPool 接口,您能够依照本人喜爱的任何形式创立 ThreadPool 实现。Quartz 附带了一个简略(但十分令人满意)的线程池,名为 org.quartz.simpl.SimpleThreadPool。此 ThreadPool 只是在其池中保护一组固定的线程 - 永不增长,永不膨胀。然而它十分强壮,并且通过了很好的测试 - 因为简直所有应用 Quartz 的人都应用该池。
这里值得一提的是,所有 JobStore 都实现了 org.quartz.spi.JobStore 接口 - 如果捆绑的 JobStore 之一不合乎您的需要,那么您能够本人制作。
最初,您须要创立 Scheduler
实例。须要给 Scheduler 自身一个名称,通知它的 RMI 设置,并传递 JobStore 和 ThreadPool 的实例。RMI 设置包含调度程序是否应将其本身创立为 RMI 的服务器对象(使其可用于近程连贯),要应用的主机和端口等。StdSchedulerFactory(上面探讨)还能够产生实际上是代理的 Scheduler 实例(RMI 存根)到在近程过程中创立的调度程序。
StdSchedulerFactory
StdSchedulerFactory 是 org.quartz.SchedulerFactory 接口的实现。它应用一组属性(java.util.Properties)创立和初始化 Quartz Scheduler。
这些属性通常存储在文件中并从文件中加载,然而也能够由程序创立并间接交给工厂。只需在工厂上调用 getScheduler()即可生成调度程序,对其进行初始化(及其 ThreadPool,JobStore 和 DataSources),并将句柄返回其公共接口。
Quartz 发行版的“docs / config”目录中有一些示例配置(包含属性阐明)。您能够在 Quartz 文档的“Reference”局部下的“Configuration”手册中找到残缺的文档。
DirectSchedulerFactory
DirectSchedulerFactory 是另一个 SchedulerFactory 实现。对于心愿以更具编程性的形式创立其 Scheduler 实例的用户来说,这很有用。通常不倡议应用它,起因如下:
- 它要求用户对他们的工作有更深刻的理解
- 它不容许进行申明式配置 - 换句话说,您最终会很难 - 对所有调度程序的设置进行编码。
日志
Quartz 应用 SLF4J 框架来满足其所有日志记录需要。为了“调整”日志记录设置(例如输入的数量以及输入的输入地位),您须要理解 SLF4J 框架,这不在本文档的探讨范畴之内。
如果要捕捉无关触发器触发和作业执行的其余信息,则可能对启用 org.quartz.plugins.history.LoggingJobHistoryPlugin
或 org.quartz.plugins.history.LoggingTriggerHistoryPlugin
感兴趣。
11. 高级性能
群集以后可与 JDBC-Jobstore(JobStoreTX 或 JobStoreCMT)和 TerracottaJobStore 一起应用。性能包含负载平衡和作业故障转移(如果 JobDetail 的“申请复原”标记设置为 true)。
应用 JobStoreTX 或 JobStoreCMT 进行群集通过将 org.quartz.jobStore.isClustered
属性设置为“true”来启用群集。
集群中的每个实例都应应用 quartz.properties 文件的雷同正本。例外情况是应用雷同的属性文件,但容许以下例外:不同的线程池大小和 org.quartz.scheduler.instanceId
属性的不同值。
集群中的每个节点必须具备惟一的 instanceId,能够通过将“AUTO”搁置为该属性的值来轻松实现(不须要其余属性文件)。
切勿在独自的计算机上运行集群,除非应用十分定期运行的某种模式的工夫同步服务(守护程序)同步它们的时钟(时钟之间的工夫距离必须在 1 秒钟之内)。如果您不相熟此操作,请参阅 http://www.boulder.nist.gov/t…。
切勿针对其余实例正在运行的同一组表启动非集群实例。您可能会遇到重大的数据损坏,并且必定会遇到不稳固的行为
每次触发时,只有一个节点将触发该作业。我的意思是,如果作业具备反复的触发器,通知它每 10 秒触发一次,则在 12:00:00 恰好一个节点将运行该作业,而在 12:00:10 恰好一个节点将运行作业等等。不肯定每次都在同一个节点上 - 哪个节点运行它或多或少是随机的。对于忙碌的调度程序(大量触发器),负载平衡机制简直是随机的,但偏差于对于非忙碌的调度程序(例如,一个或两个触发器)仅处于活动状态的同一节点。
应用 TerracottaJobStore 进行集群只需配置调度程序以应用 TerracottaJobStore(已在 第 9 课:JobStores 中介绍),您的调度程序将全副设置为集群。
您可能还须要思考如何设置 Terracotta 服务器的含意,特地是关上诸如持久性等性能以及为 HA 运行一系列 Terracotta 服务器的配置选项。
TerracottaJobStore 企业版提供高级 Quartz Where 性能,可将作业智能地定向到适当的群集节点。
JTA 事物
JobStoreCMT 容许在较大的 JTA 事务中执行 Quartz 调度操作。
通过将 org.quartz.scheduler.wrapJobExecutionInUserTransaction
属性设置为 true
,作业还能够在 JTA 事务(UserTransaction)中执行。
设置此选项后,JTA 事务将在 Job 的 execute 办法被调用之前开始 begin(),而 execute 调用终止后将进行 commit()。这实用于所有作业。
如果要为每个作业批示 JTA 事务是否应该包装其执行,则应在作业类上应用 @ExecuteInJTATransaction
批注。
除了 Quartz 在 JTA 事务中主动包装 Job 执行之外,在应用 JobStoreCMT 时,您在 Scheduler 接口上进行的调用也会参加事务。只需确保已启动事务,而后再调用调度程序上的办法即可。您能够通过应用 UserTransaction 来间接执行此操作,也能够将应用调度程序的代码放在应用容器治理的事务的 SessionBean 中。
12. 其余性能
Quartz 提供了一个用于插入附加性能的接口(org.quartz.spi.SchedulerPlugin)。
能够在*org.quartz.plugins* 包中找到 Quartz 附带的提供各种实用程序性能的插件。它们提供了一些性能,例如在调度程序启动时主动调度作业,记录作业和触发事件的历史记录,并确保在 JVM 退出时调度程序齐全敞开。
JobFactory
触发触发器时,将通过在 Scheduler 上配置的 JobFactory 实例化与之关联的 Job。默认的 JobFactory 仅在作业类上调用 newInstance()。
您可能须要创立本人的 JobFactory 实现,以实现诸如使应用程序的 IoC 或 DI 容器生成 / 初始化作业实例之类的事件。
Factory-Shipped Jobs
Quartz 还提供了许多实用程序作业,您能够在应用程序中应用它们来实现诸如发送电子邮件和调用 EJB 之类的事件。这些开箱即用的作业能够在*org.quartz.jobs* 包中找到。