公衆企業應用幾乎都會碰到任務調度的需求,就拿論壇來講:每隔半個小時生成精華文章的RSS文件,天天凌晨統計論壇用戶的積分排名,每隔30分鐘執行鎖定用戶解鎖任務。任務調度自己涉及到多線程併發、運行時間規則制定和解析、場景保持與恢復、線程池維護等諸多方面的工做。若是直接使用自定義線程這種刀耕火種的原始辦法,開發任務調度程序是一項頗具挑戰性的工做。Java開源的好處就是:領域問題都能找到現成的解決方案。java
OpenSymphony所提供的Quartz自2001年發佈版本以來已經被衆多項目做爲任務調度的解決方案,Quartz在提供巨大靈活性的同時並未犧牲其簡單性,它所提供的強大功能使你能夠應付絕大多數的調度需求。Quartz 在開源任務調度框架中的翹首,它提供了強大任務調度機制,難能難得的是它同時保持了使用的簡單性。Quartz 容許開發人員靈活地定義觸發器的調度時間表,並能夠對觸發器和任務進行關聯映射。此外,Quartz提供了調度運行環境的持久化機制,能夠保存並恢復調度現場,即便系統因故障關閉,任務調度現場數據並不會丟失。此外,Quartz還提供了組件式的偵聽器、各類插件、線程池等功能。mysql
Quartz最新版本是2.1.3,Quartz是經過藉助關係數據庫和JDBC做業存儲來實現集羣管理的,集羣經過故障切換和負載平衡的功能,能給調度器帶來高可用性和伸縮性。spring
目前Quartz集羣只能工做在JDBC-Jobstore (JobStoreTX 或者JobStoreCMT)方式下,從本質上來講,是使集羣上的每個節點經過共享同一個數據庫來工做的(Quartz經過啓動兩個維護線程來維護數據庫狀態實現集羣管理,一個是檢測節點狀態線程,一個是恢復任務線程)。sql
負載平衡是自動完成的,集羣的每一個節點會盡快觸發任務。當一個觸發器的觸發時間到達時,第一個節點將會得到任務(經過鎖定),成爲執行任務的節點。數據庫
故障切換的發生是在當一個節點正在執行一個或者多個任務失敗的時候。當一個節點失敗了,其餘的節點會檢測到而且標識在失敗節點上正在進行的數據庫中的任務。express
任何被標記爲可恢復(任務詳細信息的"requests recovery"屬性)的任務都會被其餘的節點從新執行;沒有標記可恢復的任務只會被釋放出來,將會在下次相關觸發器觸發時執行。apache
集羣經過故障切換和負載平衡的功能,能給調度器帶來高可用性和伸縮性。目前集羣只能工做在JDBC- Jobstore (JobStoreTX或者JobStoreCMT)方式下,從本質上來講,是使集羣上的每個節點經過共享同一個數據庫來工做的(Quartz經過啓動兩個維護線程來維護數據庫狀態實現集羣管理,一個是檢測節點狀態線程,一個是恢復任務線程)。負載平衡是自動完成的,集羣的每一個節點會盡快觸發任務。當一個觸發器的觸發時間到達時,第一個節點將會得到任務(經過鎖定),成爲執行任務的節點。故障切換的發生是在當一個節點正在執行一個或者多個任務失敗的時候。當一個節點失敗了,其餘的節點會檢測到而且標識在失敗節點上正在進行的數據庫中的任務。任何被標記爲可恢復(任務詳細信息的"requests recovery"屬性)的任務都會被其餘的節點從新執行。沒有標記可恢復的任務只會被釋放出來,將會在下次相關觸發器觸發時執行。windows
Quartz對任務調度的領域問題進行了高度的抽象,提出了調度器、任務和觸發器這3個核心的概念,並在org.quartz經過接口和類對重要的這些核心概念進行描述:服務器
●Job:是一個接口,只有一個方法void execute(JobExecutionContext context),開發者實現該接口定義運行任務,JobExecutionContext類提供了調度上下文的各類信息。Job運行時的信息保存在JobDataMap實例中。
●JobDetail:Quartz在每次執行Job時,都從新建立一個Job實例,因此它不直接接受一個Job的實例,相反它接收一個Job實現類,以便運行時經過newInstance()的反射機制實例化Job。所以須要經過一個類來描述Job的實現類及其它相關的靜態信息,如Job名字、描述、關聯監聽器等信息,JobDetail承擔了這一角色。
經過該類的構造函數能夠更具體地瞭解它的功用:JobDetail(java.lang.String name, java.lang.String group, java.lang.Class jobClass),該構造函數要求指定Job的實現類,以及任務在Scheduler中的組名和Job名稱。
●Trigger:是一個類,描述觸發Job執行的時間觸發規則。主要有SimpleTrigger和CronTrigger這兩個子類。當僅需觸發一次或者以固定時間間隔週期執行,SimpleTrigger是最適合的選擇;而CronTrigger則能夠經過Cron表達式定義出各類複雜時間規則的調度方案:如每早晨9:00執行,周1、周3、週五下午5:00執行等。
●Calendar:org.quartz.Calendar和java.util.Calendar不一樣,它是一些日曆特定時間點的集合(能夠簡單地將org.quartz.Calendar看做java.util.Calendar的集合——java.util.Calendar表明一個日曆時間點,無特殊說明後面的Calendar即指org.quartz.Calendar)。一個Trigger能夠和多個Calendar關聯,以便排除或包含某些時間點。假設,咱們安排每週星期一早上10:00執行任務,可是若是碰到法定的節日,任務則不執行,這時就須要在Trigger觸發機制的基礎上使用Calendar進行定點排除。針對不一樣時間段類型,Quartz在org.quartz.impl.calendar包下提供了若干個Calendar的實現類,如AnnualCalendar、MonthlyCalendar、WeeklyCalendar分別針對每一年、每個月和每週進行定義。
●Scheduler:表明一個Quartz的獨立運行容器,Trigger和JobDetail能夠註冊到Scheduler中,二者在Scheduler中擁有各自的組及名稱,組及名稱是Scheduler查找定位容器中某一對象的依據,Trigger的組及名稱必須惟一,JobDetail的組和名稱也必須惟一(但能夠和Trigger的組和名稱相同,由於它們是不一樣類型的)。Scheduler定義了多個接口方法,容許外部經過組及名稱訪問和控制容器中Trigger和JobDetail。
Scheduler能夠將Trigger綁定到某一JobDetail中,這樣當Trigger觸發時,對應的Job就被執行。一個Job能夠對應多個Trigger,但一個Trigger只能對應一個Job。能夠經過SchedulerFactory建立一個Scheduler實例。Scheduler擁有一個SchedulerContext,它相似於ServletContext,保存着Scheduler上下文信息,Job和Trigger均可以訪問SchedulerContext內的信息。SchedulerContext內部經過一個Map,以鍵值對的方式維護這些上下文數據,SchedulerContext爲保存和獲取數據提供了多個put()和getXxx()的方法。能夠經過Scheduler# getContext()獲取對應的SchedulerContext實例。
●ThreadPool:Scheduler使用一個線程池做爲任務運行的基礎設施,任務經過共享線程池中的線程提升運行效率。
咱們使用quartz-2.1.3.zip,由於Quartz集羣依賴於數據庫,因此必須首先建立Quartz數據庫表,Quartz包括了全部被支持的數據庫平臺的SQL腳本。
在 <quartz_home>/docs/dbTables 目錄下找到那些SQL腳本(這裏的 <quartz_home> 是解壓Quartz分發包後的目錄),這裏採用的Quartz 2.1.3版本,總共11張表,不一樣版本,表個數可能不一樣。數據庫爲oracle,用tables_oracle.sql建立數據庫表。
所有表以下所示。
表名 |
描述 |
QRTZ_BLOG_TRIGGERS |
Trigger做爲Blob類型存儲(用於Quartz用戶用JDBC建立他們本身定製的Trigger類型,JobStore 並不知道如何存儲實例的時候) |
QRTZ_CALENDARS |
以Blob類型存儲Quartz的Calendar信息 |
QRTZ_CRON_TRIGGERS |
存儲Cron Trigger,包括Cron表達式和時區信息 |
QRTZ_FIRED_TRIGGERS |
存儲與已觸發的Trigger相關的狀態信息,以及相聯Job的執行信息 |
QRTZ_JOB_DETAILS |
存儲每個已配置的Job的詳細信息 |
QRTZ_LOCKS |
存儲程序的非觀鎖的信息(假如使用了悲觀鎖) |
QRTZ_PAUSED_TRIGGER_GRPS |
存儲已暫停的Trigger組的信息 |
QRTZ_SCHEDULER_STATE |
存儲少許的有關 Scheduler的狀態信息,和別的 Scheduler 實例(假如是用於一個集羣中) |
QRTZ_JOB_LISTENERS |
存儲有關已配置的 JobListener的信息 |
QRTZ_SIMPLE_TRIGGERS |
存儲簡單的 Trigger,包括重複次數,間隔,以及已觸的次數 |
QRTZ_SIMPROP_TRIGGERS |
|
QRTZ_TRIGGERS |
存儲已配置的 Trigger的信息 |
說明:tables_oracle.sql裏有相應的dml初始化
說明:保存job詳細信息,該表須要用戶根據實際狀況初始化
job_name: 集羣中job的名字,該名字用戶本身能夠隨意定製,無強行要求。
job_group: 集羣中job的所屬組的名字,該名字用戶本身隨意定製,無強行要求。
job_class_name:集羣中job實現類的徹底包名,quartz就是根據這個路徑到classpath找到該job類的。
is_durable:是否持久化,把該屬性設置爲1,quartz會把job持久化到數據庫中
job_data:一個blob字段,存放持久化job對象。
存儲與已觸發的 Trigger 相關的狀態信息,以及相聯 Job 的執行信息。
說明:集羣中note實例信息,quartz定時讀取該表的信息判斷集羣中每一個實例的當前狀態。
instance_name: 配置文件中org.quartz.scheduler.instanceId配置的名字,就會寫 入該字段,若是設置爲AUTO,quartz會根據物理機名和當前時間產生一個名字。
last_checkin_time: 上次檢查時間
checkin_interval: 檢查間隔時間
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表達式
trigger_name: qrtz_triggers表trigger_name的外鍵
trigger_group: qrtz_triggers表trigger_group的外鍵
cron_expression:cron表達式
默認文件名稱quartz.properties,經過設置"org.quartz.jobStore.isClustered"屬性爲"true"來激活集羣特性。在集羣中的每個實例都必須有一個惟一的"instance id" ("org.quartz.scheduler.instanceId" 屬性), 可是應該有相同的"scheduler instance name" ("org.quartz.scheduler.instanceName"),也就是說集羣中的每個實例都必須使用相同的quartz.properties 配置文件。
除了如下幾種例外,配置文件的內容其餘都必須相同:
a.不一樣的線程池大小。
b.不一樣的"org.quartz.scheduler.instanceId"屬性值(這個能夠很容易作到,設定爲"AUTO"便可)。
注意:
1、永遠不要在不一樣的機器上運行集羣,除非他們的時鐘是使用某種形式的同步服務(守護)很是有規律的運行(時鐘必須在一分一秒內)來達到同步。
2、永遠不要觸發一個非集羣的實例,若是其餘的實例正在同一個數據庫表上運行。你將使你的數據嚴重腐蝕,出現非預期行爲。
實現一個job
package quartz.test; import java.util.Date; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; //若是有想反覆執行的動做,做業,任務就把相關的代碼寫在execute這個方法裏,前提:實現Job這個接口 //至於SimpleJob這個類何時實例化,execute這個方法什麼時候被調用,咱們不用關注,交給Quartz public class SimpleJob implements Job { private static Log _log = LogFactory.getLog(SimpleJob.class); public SimpleJob() { } public void execute(JobExecutionContext context) throws JobExecutionException { //這個做業只是簡單的打印出做業名字和此做業運行的時間 String jobName = context.getJobDetail().getFullName(); _log.info("SimpleJob says: " + jobName + " executing at " + new Date()); System.out.println("-Calling the job ---------shang- " + new Date()); } } |
定義調度器對上面的job進行調度
package quartz.test;
import java.util.Date; import org.quartz.JobDetail; import org.quartz.Scheduler; import org.quartz.SchedulerFactory; import org.quartz.SchedulerMetaData; import org.quartz.SimpleTrigger; import org.quartz.TriggerUtils; import org.quartz.impl.StdSchedulerFactory; import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.Log; /** * 這個例子展現Quartz的 簡單觸發器 所具有的基本調度能力 */ public class SimpleTriggerExample {
public void run() throws Exception { Log log = LogFactory.getLog(SimpleTriggerExample.class); log.info("------- 初始化 --------"); // 首先咱們得得到一個調度器 SchedulerFactory sf = new StdSchedulerFactory(); Scheduler sched = sf.getScheduler(); log.info("------- 初始化結束 --------"); log.info("------- 調度一個做業,簡單的說就是給一個任務定一個時間啓動 ----------------"); // 全部的做業均可以在sched.start()被調用前調度 // 獲取下個15秒的開始,如當前是10秒,則在15秒執行,如實16秒,則在30秒執行 // 若是不喜歡這個方法,本身定義一個Date也能夠 long ts = TriggerUtils.getNextGivenSecondDate(null, 15).getTime(); // job1將在ts這個時間觸發執行,咱們的做業叫job1,屬於group1組 JobDetail job = new JobDetail("job1", "group1", SimpleJob.class); SimpleTrigger trigger = new SimpleTrigger("trigger1", "group1", new Date(ts));//此處ts能夠本身設置一個Date trigger.setRepeatCount(100); trigger.setRepeatInterval(5*1000L); // 將做業加入調度隊列,返回做業執行時間 Date ft = sched.scheduleJob(job, trigger);
sched.start();//調度器啓動做業,至關於神舟7號點火了,具體何時飛天,還得看火着的速度 log.info(job.getFullName() + " 將在: " + ft + " 執行 而且重複執行: " + trigger.getRepeatCount() + " 次, 每次執行間隔 " + trigger.getRepeatInterval() / 1000 + " 秒");
log.info("------- 等待1分鐘... ------------"); try { // wait five minutes to show jobs Thread.sleep(1000*60); // executing... } catch (Exception e) { } log.info("------- 關閉調度器 --------"); sched.shutdown(true); log.info("------- 關閉結束---------"); } public static void main(String[] args) throws Exception { SimpleTriggerExample example = new SimpleTriggerExample(); example.run(); } }
|
配置文件最好本身指定一個,不然會使用quartz.jar包裏面的默認quartz.properties文件
#===================================================================== # Configure Main Scheduler Properties 主要屬性配置 #===================================================================== org.quartz.scheduler.instanceName = Scheduler org.quartz.scheduler.instanceId = AUTO org.quartz.scheduler.rmi.export = false org.quartz.scheduler.rmi.proxy = false
#===================================================================== # Configure ThreadPool 線程池配置 #===================================================================== org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool org.quartz.threadPool.threadCount = 3 org.quartz.threadPool.threadPriority = 5
#===================================================================== # jobStore 任務存儲配置 #===================================================================== #org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore #org.quartz.jobStore.misfireThreshold = MISFIRE_THRESHOLD #or org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.MSSQLDelegate org.quartz.jobStore.useProperties = false org.quartz.jobStore.dataSource = myDS org.quartz.jobStore.tablePrefix = qrtz_ org.quartz.jobStore.isClustered = false
#===================================================================== # Date Source 數據源的配置 #===================================================================== org.quartz.dataSource.myDS.driver = com.mysql.jdbc.Driver org.quartz.dataSource.myDS.URL = jdbc:mysql://localhost:3306/quartz_tables?useUnicode=true&characterEncoding=utf8 org.quartz.dataSource.myDS.user = root org.quartz.dataSource.myDS.password = 123456 org.quartz.dataSource.myDS.maxConnections = 5 |
爲 Quartz 配置集羣環境的步驟比設置相似的JAVAEE集羣環境容易的多:
配置每一個節點的 quartz.properties 文件。
配置 JDBC JobStore。
使用Scheduler信息(Job和Trigger) 裝載數據庫。
啓動每一個 Quartz 節點。
5. 經過設置"org.quartz.jobStore.isClustered"屬性爲"true"來激活集羣特性。在集羣中的每個實例都必須有一個惟一的"instance id" ("org.quartz.scheduler.instanceId" 屬性), 可是應該有相同的"scheduler instance name" ("org.quartz.scheduler. instanceName"),也就是說集羣中的每個實例都必須使用相同的 quartz.properties配置文件。除了如下幾種例外,配置文件的內容其餘都必須相同:不一樣的線程池大小,不一樣的"org.quartz.scheduler.instanceId"屬性值(這個能夠很容易作到,設定爲"AUTO"便可)。
注意: 永遠不要在不一樣的機器上運行集羣,除非他們的時鐘是使用某種形式的同步服務(守護)很是有規律的運行(時鐘必須在一分一秒內)來達到同步。還有: 永遠不要觸發一個非集羣的實例,若是其餘的實例正在同一個數據庫表上運行。你將使你的數據嚴重腐蝕,出現非預期行爲。
環境:
Windows xp 主機:
Vmware 7虛擬的兩臺RHEL5:
Server A:192.168.255.130
Server B:192.168.255.131(防火牆已關閉)
Mysql數據庫 安裝在windows 主機上
Server A:
quartz.properties文件配置信息以下:
#=====================================================================# Configure Main Scheduler Properties #===================================================================== org.quartz.scheduler.instanceName = myScheduler org.quartz.scheduler.instanceId = AUTO #===================================================================== # Configure ThreadPool #===================================================================== org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool org.quartz.threadPool.threadCount = 3 org.quartz.threadPool.threadPriority = 5 #===================================================================== # Configure jobStore #===================================================================== org.quartz.jobStore.misfireThreshold = 60000 org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.MSSQLDelegate org.quartz.jobStore.dataSource = myDS org.quartz.jobStore.tablePrefix = qrtz_ #是否集羣 org.quartz.jobStore.isClustered = true org.quartz.jobStore.clusterCheckinInterval = 200 #===================================================================== # Non-Managed Configure Date Source #===================================================================== org.quartz.dataSource.myDS.driver = com.mysql.jdbc.Driver org.quartz.dataSource.myDS.URL = jdbc:mysql://10.3.43.73:3306/quartz_cluster?useUnicode=true&characterEncoding=utf8 org.quartz.dataSource.myDS.user = root org.quartz.dataSource.myDS.password = 123456 org.quartz.dataSource.myDS.maxConnections = 5
|
Java代碼:
A: package cluster;
package cluster; import java.util.Date; import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; public class SimpleJob implements Job {
public SimpleJob() { } public void execute(JobExecutionContext context) throws JobExecutionException { //String jobName = context.getJobDetail().getFullName(); System.out.println("A Calling the job ---------shang- " + new Date()); } } |
B: package cluster
package cluster;
import java.util.Date; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.quartz.JobDetail; import org.quartz.Scheduler; import org.quartz.SchedulerFactory; import org.quartz.SimpleTrigger; import org.quartz.TriggerUtils; import org.quartz.impl.StdSchedulerFactory;
public class SimpleTriggerExample { public void run() throws Exception { Log log = LogFactory.getLog(SimpleTriggerExample.class); log.info("------- initial--------"); SchedulerFactory sf = new StdSchedulerFactory(); Scheduler sched = sf.getScheduler(); log.info("------- initial end--------"); log.info("------- scheduler a job----------"); long ts = TriggerUtils.getNextGivenSecondDate(null, 15).getTime(); JobDetail job = new JobDetail("job1", "group1", SimpleJob.class); SimpleTrigger trigger = new SimpleTrigger("trigger1", "group1", new Date(ts)); trigger.setRepeatCount(100); trigger.setRepeatInterval(5*1000L); Date ft = sched.scheduleJob(job, trigger); //sched.start(); log.info(job.getFullName() + " time: " + ft + "repeatcount: " + trigger.getRepeatCount() + "repeatInterval" + trigger.getRepeatInterval() / 1000 + " s"); log.info("------- wait 1 minite----------"); try { // wait five minutes to show jobs Thread.sleep(1000*60); // executing... } catch (Exception e) { } log.info("------- close scheduler---------------------"); sched.shutdown(true); log.info("------- end----------------"); } public static void main(String[] args) throws Exception { SimpleTriggerExample example = new SimpleTriggerExample(); example.run(); } } |
C: package cluster
package cluster;
import org.quartz.*; import org.quartz.impl.StdSchedulerFactory;
public class JobreScheduler { public static void main(String args[]){ try{ SchedulerFactory sf = new StdSchedulerFactory(); Scheduler sche = sf.getScheduler(); sche.start(); System.out.println("C scheduler start"); }catch (Exception e){ e.printStackTrace(); } } } |
Server B:
quartz.properties文件配置信息以下:
#=====================================================================# Configure Main Scheduler Properties #===================================================================== org.quartz.scheduler.instanceName = myScheduler org.quartz.scheduler.instanceId = AUTO #===================================================================== # Configure ThreadPool #===================================================================== org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool org.quartz.threadPool.threadCount = 3 org.quartz.threadPool.threadPriority = 5 #===================================================================== # Configure jobStore #===================================================================== org.quartz.jobStore.misfireThreshold = 60000 org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.MSSQLDelegate org.quartz.jobStore.dataSource = myDS org.quartz.jobStore.tablePrefix = qrtz_
#是否集羣 org.quartz.jobStore.isClustered = true org.quartz.jobStore.clusterCheckinInterval = 200 #===================================================================== # Non-Managed Configure Date Source #===================================================================== org.quartz.dataSource.myDS.driver = com.mysql.jdbc.Driver org.quartz.dataSource.myDS.URL = jdbc:mysql://10.3.43.73:3306/quartz_cluster?useUnicode=true&characterEncoding=utf8 org.quartz.dataSource.myDS.user = root org.quartz.dataSource.myDS.password = 123456 org.quartz.dataSource.myDS.maxConnections = 5
|
Java代碼:
A:
package cluster; import java.util.Date; import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; public class SimpleJob implements Job {
public SimpleJob() { } public void execute(JobExecutionContext context) throws JobExecutionException { //String jobName = context.getJobDetail().getFullName(); System.out.println("B Calling the job ---------shang- " + new Date()); } }
|
B:
package cluster;
import java.util.Date; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.quartz.JobDetail; import org.quartz.Scheduler; import org.quartz.SchedulerFactory; import org.quartz.SimpleTrigger; import org.quartz.TriggerUtils; import org.quartz.impl.StdSchedulerFactory;
public class SimpleTriggerExample { public void run() throws Exception { Log log = LogFactory.getLog(SimpleTriggerExample.class); log.info("------- initial--------"); SchedulerFactory sf = new StdSchedulerFactory(); Scheduler sched = sf.getScheduler(); log.info("------- initial end--------"); log.info("------- scheduler a job----------"); long ts = TriggerUtils.getNextGivenSecondDate(null, 15).getTime(); JobDetail job = new JobDetail("job1", "group1", SimpleJob.class); SimpleTrigger trigger = new SimpleTrigger("trigger1", "group1", new Date(ts)); trigger.setRepeatCount(100); trigger.setRepeatInterval(5*1000L); Date ft = sched.scheduleJob(job, trigger); //sched.start(); log.info(job.getFullName() + " time: " + ft + "repeatcount: " + trigger.getRepeatCount() + "repeatInterval" + trigger.getRepeatInterval() / 1000 + " s"); log.info("------- wait 1 minite----------"); try { // wait five minutes to show jobs Thread.sleep(1000*60); // executing... } catch (Exception e) { } log.info("------- close scheduler---------------------"); sched.shutdown(true); log.info("------- end----------------"); } public static void main(String[] args) throws Exception { SimpleTriggerExample example = new SimpleTriggerExample(); example.run(); } } |
C:
package cluster;
import org.quartz.*; import org.quartz.impl.StdSchedulerFactory;
public class JobreScheduler { public static void main(String args[]){ try{ SchedulerFactory sf = new StdSchedulerFactory(); Scheduler sche = sf.getScheduler(); sche.start(); System.out.println("B scheduler start"); }catch (Exception e){ e.printStackTrace(); } } } |
Server A與Server B中的配置和代碼徹底同樣
運行方法:
運行任意主機上的程序代碼B,將任務加入調度;
運行兩臺主機上的代碼C
觀察運行結果
集羣是否配置成功的評判標準:
1、啓動兩臺服務器後只有一臺在運行調度程序,另一臺等待。
2、查看數據庫中的表項,能夠看到兩個集羣實例。以下圖所示。
中斷任意服務器,另外一臺服務器會啓動任務調度,繼續運行任務。
1、Quartz代碼庫(使用SVN客戶端打開):http://svn.terracotta.org/svn/quartz
2、Quartz主頁:http://www.quartz-scheduler.org
3、本文檔中的代碼下載地址:http://download.csdn.net/detail/thinkhejie/4195973
<bean id="schedulerFactoryBean"
class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="dataSource">
<ref bean="dataSource_blog"/>
</property><property
name="applicationContextSchedulerContextKey" value="applicationContext"/>
<property name="configLocation" value="classpath:quartz.properties"/>
<!--這個是必須的,QuartzScheduler 延時啓動,應用啓動完後 QuartzScheduler 再啓動 -->
<property name="startupDelay" value="60"/>
<!--這個是可選,QuartzScheduler 啓動時更新己存在的Job,這樣就不用每次修改targetObject後刪除qrtz_job_details表對應記錄了 -->
<property name="overwriteExistingJobs" value="true"/>
<property name="triggers">
<!-- 多個Trigger在此配置 -->
<list>
<ref bean="pushTimerTrigger" />
<ref bean="topTimerTrigger" />
<ref bean="scoreTimerTrigger" />
<ref bean="billingTimerTrigger" />
<ref bean="volumeIssueTimerTrigger" />
<ref bean="favoriteUserTimerTrigger" />
<ref bean="processBalanceTimerTrigger" />
<ref bean="subsUserTimerTrigger" />
</list>
</property>
</bean>
注意:
1.dataSource:項目中用到的數據源,裏面包含了quartz用到的12張數據庫表;這 裏使用jboss jndi鏈接數據庫作quartz集羣,並將dataSource_blog注入SchedulerFactoryBean。
2. applicationContextSchedulerContextKey:
org.springframework.scheduling.quartz.SchedulerFactoryBean這個類中把spring上下 文以key/value的方式存放在了quartz的上下文中了,能夠用applicationContextSchedulerContextKey所 定義的key獲得對應的spring上下文;
3. configLocation:用於指明quartz的配置文件的位置
#==============================================================
#Configure Main Scheduler Properties
#==============================================================
org.quartz.scheduler.instanceName = TestScheduler1
org.quartz.scheduler.instanceId = AUTO
#==============================================================
#Configure ThreadPool
#==============================================================
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = 2
org.quartz.threadPool.threadPriority = 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true
#==============================================================
#Configure JobStore
#==============================================================
org.quartz.jobStore.misfireThreshold = 60000
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.tablePrefix = QRTZ_
org.quartz.jobStore.maxMisfiresToHandleAtATime=10
org.quartz.jobStore.isClustered = true
org.quartz.jobStore.clusterCheckinInterval = 20000
注意:
org.quartz.scheduler.instanceName屬性可爲任何值,用在 JDBC JobStore 中來惟一標識實例,可是全部集羣節點中必須相同。
org.quartz.scheduler.instanceId 屬性爲 AUTO便可,基於主機名和時間戳來產生實例 ID。
org.quartz.jobStore.class屬性爲 JobStoreTX,將任務持久化到數據中。由於集羣中節點依賴於數據庫來傳播 Scheduler 實例的狀態,你只能在使用 JDBC JobStore 時應用 Quartz 集羣。這意味着你必須使用 JobStoreTX 或是 JobStoreCMT 做爲 Job 存儲;你不能在集羣中使用 RAMJobStore。
org.quartz.jobStore.isClustered 屬性爲 true,你就告訴了 Scheduler 實例要它參與到一個集羣當中。這一屬性會貫穿於調度框架的始終,用於修改集羣環境中操做的默認行爲。
org.quartz.jobStore.clusterCheckinInterval 屬性定義了Scheduler 實例檢入到數據庫中的頻率(單位:毫秒)。Scheduler 檢查是否其餘的實例到了它們應當檢入的時候未檢入;這能指出一個失敗的 Scheduler 實例,且當前 Scheduler 會以此來接管任何執行失敗並可恢復的 Job。經過檢入操做,Scheduler 也會更新自身的狀態記錄。clusterChedkinInterval 越小,Scheduler 節點檢查失敗的 Scheduler 實例就越頻繁。默認值是 15000 (即15 秒)。
<bean id="billingTimerJob" class="frameworkx.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="targetObject" ref="billingTimer" />
<property name="targetMethod" value="createPreSeasontimer" />
<property name="shouldRecover" value="true"/>
</bean>
<!-- cronExpression的介紹: 按順序 <value> 秒 分 小時 日期 月份 星期 年<value> 字段 容許值 容許的特殊字符:秒 0-59 , - * / 分 0-59 , - * / 小時 0-23 , - * / 日期 1-31 , - * ? / L W C,月份 1-12 或者 JAN-DEC , - * / 星期 1-7 或者 SUN-SAT , - * ? / L C # 年(可選)
留空,1970-2099 , - * / 「*」字符被用來指定全部的值。如:」*「在分鐘的字段域裏表示「每分鐘」。參考《http://www.cnblogs.com/xiaopeng84/archive/2009/11/26/1611427.html》-->
<beanid="billingTimerTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">
<property name="jobDetail" ref="billingTimerJob" />
<property name="cronExpression" value="0 0 1 1 1,4,7,10 ?" />
</bean>
注:關於Job配置,這裏有兩點須要注意
MethodInvokingJobDetailFactoryBean
在這裏使用牛人修改後的 frameworkx.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean,能夠參考: http://jira.springframework.org/browse/SPR-3797。
直接使用org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean會報java.io.NotSerializableException異常。
2. shouldRecover
shouldRecover屬性必須設置爲 true,當Quartz服務被停止後,再次啓動或集羣中其餘機器接手任務時會嘗試恢復執行以前未完成的全部任務。
由於Job須要持久化到數據庫中,BillingTimer必須實現Serializable接口,另外在BillingTimer引入一個公共的service(SpringBeanService)應用範型並經過ApplicationContext從spring下文獲取serviceBean實例來實現調用BillingTimerService,
從而解決BillingTimerService調用BillingTimerDao進而調用SimpleJdbcTemplate時,SimpleJdbcTemplate不通被序列化的問題。
BillingTimer.java源代碼清單以下:
@Component
public class BillingTimer implements Serializable {
private static final long serialVersionUID = -8961129525628230999L;
@Autowired private SpringBeanService springBeanService;
private static final Log log = Log.getInstance(BillingTimer.class);
public void createPreSeasontimer() {
int[] num = null;
try {
num = springBeanService.getBean(BillingTimerService.class,
"billingTimerService").createseasonBill();
if (null != num) {
log.info("ok");
} else {
log.error("failed");
}
} catch (Exception e) {
log.error(e.getMessage());
e.printStackTrace();
}
}
}
SpringBeanService源代碼清單以下:
public class SpringBeanService implements Serializable{
private static final long serialVersionUID = -2228376078979553838L;
public <T> T getBean(Class<T> clazz,String beanName){
ApplicationContext context = ContextLoader.getCurrentWebApplicationContext();
return (T)context.getBean(beanName);
}
}