深入解讀Quartz任務(wù)調(diào)度器
1.Quartz簡介
1.1.概要
Quartz是OpenSymphony提供的強(qiáng)大的開源任務(wù)調(diào)度框架。
官網(wǎng):http://www.quartz-scheduler.org
純Java實(shí)現(xiàn),精細(xì)控制排程。
1.2.Quartz特點(diǎn)
- 強(qiáng)大的調(diào)度能力
- 靈活的應(yīng)用方式
- 強(qiáng)大的分布式和集群能力
1.3.Quartz設(shè)計(jì)模式
- Builder模式
- 組件模式
- Factory模式
- 鏈?zhǔn)綄懛?/li>
1.4.Quartz體系結(jié)構(gòu)
1.4.1.三大核心
- 調(diào)度器
- 任務(wù)
- 觸發(fā)器
1.4.2.重要組成
1)任務(wù):
-
Job:表示一個(gè)工作,要執(zhí)行的具體內(nèi)容。此接口中只有一個(gè)方法。要?jiǎng)?chuàng)建一個(gè)任務(wù),必須得實(shí)現(xiàn)這個(gè)接口。該接口只有一個(gè)execute方法,任務(wù)每次被調(diào)用的時(shí)候都會(huì)執(zhí)行這個(gè)execute方法的邏輯,類似TimerTask的run方法,在里面編寫業(yè)務(wù)邏輯。
public class TestJob implements Job { /**把要執(zhí)行的操作,寫在execute方法中 */ @Override public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); System.out.println("I can do something..."); System.out.println(sdf.format(new Date())); } }
生命周期:在每次調(diào)度器執(zhí)行job時(shí),它在調(diào)用execute方法前會(huì)創(chuàng)建一個(gè)新的job實(shí)例,當(dāng)調(diào)用完成之后,關(guān)聯(lián)的job對象實(shí)例會(huì)被釋放,釋放的實(shí)例會(huì)被垃圾回收機(jī)制回收。
-
JobBuilder:可向任務(wù)傳遞數(shù)據(jù),通常情況下,我們使用它就可向任務(wù)類發(fā)送數(shù)據(jù)了,如有特別復(fù)雜的傳遞參數(shù),它提供了一個(gè)傳遞遞:JobDataMap對象的方法
JobDetail jobDetail = JobBuilder.newJob(TestJob.class).withIdentity("testJob","group1").build();
JobDetail:用來保存我們?nèi)蝿?wù)的詳細(xì)信息。一個(gè)JobDetail可以有多個(gè)Trigger,但是一個(gè)Trigger只能對應(yīng)一個(gè)JobDetail。下面是JobDetail的一些常用的屬性和含義:
- JobStore:負(fù)責(zé)跟蹤所有你給scheduler的“工作數(shù)據(jù)”:jobs, triggers, calendars, 等。
-
RAMJobStore:是使用最簡單的也是最高效(依據(jù)CPU時(shí)間)的JobStore 。RAMJobStore 正如它名字描述的一樣,它保存數(shù)據(jù)在RAM。缺點(diǎn)是你的應(yīng)用結(jié)束之后所有的數(shù)據(jù)也丟失了--這意味著RAMJobStore 不具有保持job和trigger持久的能力。對于一些程序是可以接受的,甚至是期望的,但對于其他的程序可能是災(zāi)難性的。使用RAMJobStore配置Quartz:配置如下
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
-
JDBCJobStore:以JDBC的方式保存數(shù)據(jù)在數(shù)據(jù)庫中。它比RAMJobStore的配置復(fù)雜一點(diǎn),也沒有RAMJobStore快。然而,性能缺點(diǎn)不是糟透了,特別是如果你在數(shù)據(jù)庫表主鍵上建立了索引。在機(jī)器之間的LAN(在scheduler 和數(shù)據(jù)庫之間)合理的情況下,檢索和更新一個(gè)被觸發(fā)的Trigger花費(fèi)的時(shí)間少于10毫秒。幾乎適用于所有的數(shù)據(jù)庫,廣泛用于 Oracle。PostgreSQL, MySQL, MS SQLServer, HSQLDB, 和DB2。使用JDBCJobStore之前你必須首先創(chuàng)建一系列Quartz要使用的表。你可以發(fā)現(xiàn)表創(chuàng)建語句在Quartz發(fā)布目錄的 “docs/dbTables”下面。你需要確定你的應(yīng)用要使用的事務(wù)類型。如果你不想綁定調(diào)度命令(例如增加和移除Trigger)到其他的事務(wù),你可以使用JobStoreTX (最常用的選擇)作為你的Jobstore。如果你需要Quartz和其他的事務(wù)(例如在J2EE應(yīng)用服務(wù)器中)一起工作,你應(yīng)該使用JobStoreCMT ,Quartz 將讓應(yīng)用服務(wù)器容器管理這個(gè)事務(wù)。使用JobStoreTx配置Quartz:
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate #配置表的前綴 org.quartz.jobStore.tablePrefix = QRTZ_ #使用JNDI數(shù)據(jù)源的時(shí)候,數(shù)據(jù)源的名字 org.quartz.jobStore.dataSource = myDS
-
TerracottaJobStore:提供了一個(gè)方法:在不使用數(shù)據(jù)庫的情況下使它具有收縮性和強(qiáng)壯性。可以是集群的也可以是非集群的,在這兩種情況下為你的job數(shù)據(jù)提供了一個(gè)存儲機(jī)制用于應(yīng)用程序重啟之間持久,因?yàn)閿?shù)據(jù)是存儲在Terracotta服務(wù)器。它的性能比使用數(shù)據(jù)庫訪問JDBCJobStore好一點(diǎn)兒(大約是一個(gè)數(shù)量級),但是明顯比RAMJobStore慢。使用TerracottaJobStore配置Quartz:
org.quartz.jobStore.class = org.terracotta.quartz.TerracottaJobStore org.quartz.jobStore.tcConfigUrl = localhost:9510
-
- JobDataMap:中可以包含不限量的(序列化的)數(shù)據(jù)對象,在job實(shí)例執(zhí)行的時(shí)候,可以使用其中的數(shù)據(jù);JobDataMap是Java Map接口的一個(gè)實(shí)現(xiàn),額外增加了一些便于存取基本類型的數(shù)據(jù)的方法。
-
存:
JobDetail jobDetail = JobBuilder.newJob(TestJob.class).withIdentity("testJob","group1").usingJobData("date1","存內(nèi)容").build();
-
取:
public class TestJob implements Job { /**把要執(zhí)行的操作,寫在execute方法中 */ @Override public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { JobKey key = jobExecutionContext.getJobDetail().getKey(); JobDataMap jobDataMap = jobExecutionContext.getJobDetail().getJobDataMap(); String date1 = jobDataMap.getString("date1"); } }
-
2)觸發(fā)器:用來觸發(fā)執(zhí)行Job
2.1)觸發(fā)器通用屬性:
- Jobkey:表示job實(shí)例的標(biāo)識,觸發(fā)器被觸發(fā)時(shí),該指定的job實(shí)例會(huì)被執(zhí)行
- StartTime:表示觸發(fā)器的時(shí)間表首次被觸發(fā)的時(shí)間,它的值類型為:java.util.Date
- EndTime:指定觸發(fā)器的不再被觸發(fā)的時(shí)間,它的值類型為:java.util.Date
2.2)觸發(fā)器類型:
-
SimpleTrigger: 主要是針對一些相對簡單的時(shí)間觸發(fā)進(jìn)行配置使用,比如在指定的時(shí)間開始然后在一定的時(shí)間間隔之內(nèi)重復(fù)執(zhí)行一個(gè)Job,同時(shí)可以任意指定重復(fù)的次數(shù)。下面就是使用一個(gè)SimpleTrigger的例子:
//創(chuàng)建觸發(fā)器 每3秒鐘執(zhí)行一次(無開始時(shí)間和結(jié)束時(shí)間) Trigger trigger = TriggerBuilder.newTrigger() .withIdentity("trigger1", "group3") .withSchedule( SimpleScheduleBuilder.simpleSchedule() .withIntervalInSeconds(3).repeatForever()).build(); //創(chuàng)建觸發(fā)器 每3秒鐘執(zhí)行一次(有開始時(shí)間和結(jié)束時(shí)間) long now = new Date().getTime(); Date start = new Date(now+6000); Date end = new Date(now+12000); //創(chuàng)建觸發(fā)器 每3秒鐘執(zhí)行一次 Trigger trigger = TriggerBuilder.newTrigger() .withIdentity("trigger1", "group3") .startAt(start) .endAt(end) .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(3).repeatForever()).build();
SimpleTrigger具有豐富的構(gòu)造函數(shù),根據(jù)業(yè)務(wù)需求構(gòu)造不同的構(gòu)造函數(shù)。
- CronTrigger: 可以配置更復(fù)雜的觸發(fā)時(shí)刻表,基于日歷的作業(yè)觸發(fā)器,而不像SimpleTrigger那樣精確指定間隔時(shí)間,比SimpleTrigger更加常用。
Cron表達(dá)式:用于配置CronTrigger實(shí)例,是由7個(gè)表達(dá)式組成的字符串,描述了時(shí)間表的詳細(xì)信息。
格式為:[秒][分][時(shí)][日][月][周][年]
Cron表達(dá)式特殊字符意義對應(yīng)表:
通配符說明:
Cron表達(dá)式例子:
TriggerBuilder.newTrigger().withIdentity("trigger2","group2")
.withSchedule(CronScheduleBuilder.cronSchedule("0 0 9 ? * 6L *")).build();
Cron表達(dá)式小技巧:
1. ‘L’和‘W’可以一起組合使用
2. 周字段英文字母不區(qū)分大小寫即MOM與mom相同
3. 利用工具,在線生成cron表達(dá)式:http://cron.qqe2.com/
-
NthIncludedDayTrigger:是 Quartz 開發(fā)團(tuán)隊(duì)最新加入到框架中的一個(gè) Trigger。它設(shè)計(jì)用于在每一間隔類型的第幾天執(zhí)行 Job。例如,你要在每個(gè)月的 15 號執(zhí)行開票的 Job,用 NthIncludedDayTrigger就再合適不過了。
NthIncludedDayTrigger trigger = new NthIncludedDayTrigger("NthIncludedDayTrigger",Scheduler.DEFAULT_GROUP); trigger.setN(15); trigger.setIntervalType(NthIncludedDayTrigger.INTERVAL_TYPE_MONTHLY);
3)調(diào)度器Scheduler
代表一個(gè)Quartz的獨(dú)立運(yùn)行容器,Trigger和JobDetail可以注冊到Scheduler中,兩者在Scheduler中擁有各自的組及名稱,組及名稱是Scheduler查找定位容器中某一對象的依據(jù),Trigger的組及名稱必須唯一,JobDetail的組和名稱也必須唯一(但可以和Trigger的組和名稱相同,因?yàn)樗鼈兪遣煌愋偷模cheduler定義了多個(gè)接口方法,允許外部通過組及名稱訪問和控制容器中Trigger和JobDetail。
Scheduler可以將Trigger綁定到某一JobDetail中,這樣當(dāng)Trigger觸發(fā)時(shí),對應(yīng)的Job就被執(zhí)行。一個(gè)Job可以對應(yīng)多個(gè)Trigger,但一個(gè)Trigger只能對應(yīng)一個(gè)Job。
可以通過SchedulerFactory創(chuàng)建一個(gè)Scheduler實(shí)例。Scheduler擁有一個(gè)SchedulerContext,它類似于ServletContext,保存著Scheduler上下文信息,Job和Trigger都可以訪問SchedulerContext內(nèi)的信息。SchedulerContext內(nèi)部通過一個(gè)Map,以鍵值對的方式維護(hù)這些上下文數(shù)據(jù),SchedulerContext為保存和獲取數(shù)據(jù)提供了多個(gè)put()和getXxx()的方法。可以通過Scheduler# getContext()獲取對應(yīng)的SchedulerContext實(shí)例;
SchedulerFactory schedulerfactory=new StdSchedulerFactory();
Scheduler scheduler = schedulerfactory.getScheduler();
DirectSchedulerFactory factory = DirectSchedulerFactory.getInstance();
try {
Scheduler scheduler = factory.getScheduler();
} catch (SchedulerException e) {
e.printStackTrace();
}
4)SchedulerFactory:
- 使用一組參數(shù)(java.util.Properties)來創(chuàng)建和出書啊Quartz調(diào)度器
- 配置參數(shù)一般存儲在quartz.properties中
- 調(diào)用getScheduler方法就能創(chuàng)建和初始化調(diào)度器
5)quartz.properties:
Quartz-Job的quartz.properties配置文件說明,此文件在quartz的jar包有,可直接拿過來使用不過只有基本的幾個(gè)配置 自己可根據(jù)需要進(jìn)行擴(kuò)充;另外如果項(xiàng)目中沒有對該配置文件重寫,則Quartz會(huì)加載自己jar包中的quartz.properties文件。
# Default Properties file for use by StdSchedulerFactory
# to create a Quartz Scheduler Instance, if a different
# properties file is not explicitly specified.
#
# ===========================================================================
# Configure Main Scheduler Properties 調(diào)度器屬性
# ===========================================================================
org.quartz.scheduler.instanceName: DefaultQuartzScheduler
#org.quartz.scheduler.instanceid:AUTO
org.quartz.scheduler.rmi.export: false
org.quartz.scheduler.rmi.proxy: false
org.quartz.scheduler.wrapJobExecutionInUserTransaction: false
# ===========================================================================
# Configure ThreadPool 線程池屬性
# ===========================================================================
#線程池的實(shí)現(xiàn)類(一般使用SimpleThreadPool即可滿足幾乎所有用戶的需求)
org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
#指定線程數(shù),至少為1(無默認(rèn)值)(一般設(shè)置為1-100直接的整數(shù)合適)
org.quartz.threadPool.threadCount: 10
#設(shè)置線程的優(yōu)先級(最大為java.lang.Thread.MAX_PRIORITY 10,最小為Thread.MIN_PRIORITY 1,默認(rèn)為5)
org.quartz.threadPool.threadPriority: 5
#設(shè)置SimpleThreadPool的一些屬性
#設(shè)置是否為守護(hù)線程
#org.quartz.threadpool.makethreadsdaemons = false
#org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true
#org.quartz.threadpool.threadsinheritgroupofinitializingthread=false
#線程前綴默認(rèn)值是:[Scheduler Name]_Worker
#org.quartz.threadpool.threadnameprefix=swhJobThead;
# 配置全局監(jiān)聽(TriggerListener,JobListener) 則應(yīng)用程序可以接收和執(zhí)行 預(yù)定的事件通知
# ===========================================================================
# Configuring a Global TriggerListener 配置全局的Trigger監(jiān)聽器
# MyTriggerListenerClass 類必須有一個(gè)無參數(shù)的構(gòu)造函數(shù),和 屬性的set方法,目前2.2.x只支持原始數(shù)據(jù)類型的值(包括字符串)
# ===========================================================================
#org.quartz.triggerListener.NAME.class = com.swh.MyTriggerListenerClass
#org.quartz.triggerListener.NAME.propName = propValue
#org.quartz.triggerListener.NAME.prop2Name = prop2Value
# ===========================================================================
# Configuring a Global JobListener 配置全局的Job監(jiān)聽器
# MyJobListenerClass 類必須有一個(gè)無參數(shù)的構(gòu)造函數(shù),和 屬性的set方法,目前2.2.x只支持原始數(shù)據(jù)類型的值(包括字符串)
# ===========================================================================
#org.quartz.jobListener.NAME.class = com.swh.MyJobListenerClass
#org.quartz.jobListener.NAME.propName = propValue
#org.quartz.jobListener.NAME.prop2Name = prop2Value
# ===========================================================================
# Configure JobStore 存儲調(diào)度信息(工作,觸發(fā)器和日歷等)
# ===========================================================================
# 信息保存時(shí)間 默認(rèn)值60秒
org.quartz.jobStore.misfireThreshold: 60000
#保存job和Trigger的狀態(tài)信息到內(nèi)存中的類
org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore
# ===========================================================================
# Configure SchedulerPlugins 插件屬性 配置
# ===========================================================================
# 自定義插件
#org.quartz.plugin.NAME.class = com.swh.MyPluginClass
#org.quartz.plugin.NAME.propName = propValue
#org.quartz.plugin.NAME.prop2Name = prop2Value
#配置trigger執(zhí)行歷史日志(可以看到類的文檔和參數(shù)列表)
org.quartz.plugin.triggHistory.class = org.quartz.plugins.history.LoggingTriggerHistoryPlugin
org.quartz.plugin.triggHistory.triggerFiredMessage = Trigger {1}.{0} fired job {6}.{5} at: {4, date, HH:mm:ss MM/dd/yyyy}
org.quartz.plugin.triggHistory.triggerCompleteMessage = Trigger {1}.{0} completed firing job {6}.{5} at {4, date, HH:mm:ss MM/dd/yyyy} with resulting trigger instruction code: {9}
#配置job調(diào)度插件 quartz_jobs(jobs and triggers內(nèi)容)的XML文檔
#加載 Job 和 Trigger 信息的類 (1.8之前用:org.quartz.plugins.xml.JobInitializationPlugin)
org.quartz.plugin.jobInitializer.class = org.quartz.plugins.xml.XMLSchedulingDataProcessorPlugin
#指定存放調(diào)度器(Job 和 Trigger)信息的xml文件,默認(rèn)是classpath下quartz_jobs.xml
org.quartz.plugin.jobInitializer.fileNames = my_quartz_job2.xml
#org.quartz.plugin.jobInitializer.overWriteExistingJobs = false
org.quartz.plugin.jobInitializer.failOnFileNotFound = true
#自動(dòng)掃描任務(wù)單并發(fā)現(xiàn)改動(dòng)的時(shí)間間隔,單位為秒
org.quartz.plugin.jobInitializer.scanInterval = 10
#覆蓋任務(wù)調(diào)度器中同名的jobDetail,避免只修改了CronExpression所造成的不能重新生效情況
org.quartz.plugin.jobInitializer.wrapInUserTransaction = false
# ===========================================================================
# Sample configuration of ShutdownHookPlugin ShutdownHookPlugin插件的配置樣例
# ===========================================================================
#org.quartz.plugin.shutdownhook.class = \org.quartz.plugins.management.ShutdownHookPlugin
#org.quartz.plugin.shutdownhook.cleanShutdown = true
#
# Configure RMI Settings 遠(yuǎn)程服務(wù)調(diào)用配置
#
#如果你想quartz-scheduler出口本身通過RMI作為服務(wù)器,然后設(shè)置“出口”標(biāo)志true(默認(rèn)值為false)。
#org.quartz.scheduler.rmi.export = false
#主機(jī)上rmi注冊表(默認(rèn)值localhost)
#org.quartz.scheduler.rmi.registryhost = localhost
#注冊監(jiān)聽端口號(默認(rèn)值1099)
#org.quartz.scheduler.rmi.registryport = 1099
#創(chuàng)建rmi注冊,false/never:如果你已經(jīng)有一個(gè)在運(yùn)行或不想進(jìn)行創(chuàng)建注冊
# true/as_needed:第一次嘗試使用現(xiàn)有的注冊,然后再回來進(jìn)行創(chuàng)建
# always:先進(jìn)行創(chuàng)建一個(gè)注冊,然后再使用回來使用注冊
#org.quartz.scheduler.rmi.createregistry = never
#Quartz Scheduler服務(wù)端端口,默認(rèn)是隨機(jī)分配RMI注冊表
#org.quartz.scheduler.rmi.serverport = 1098
#true:鏈接遠(yuǎn)程服務(wù)調(diào)度(客戶端),這個(gè)也要指定registryhost和registryport,默認(rèn)為false
# 如果export和proxy同時(shí)指定為true,則export的設(shè)置將被忽略
#org.quartz.scheduler.rmi.proxy = false