quartz
在java的世界里, quartz絕對是總統山級別的王者的存在. 市面上大多數的開源的調度框架也基本都是直接或間接基於這個框架來開發的.
先來看通過一個最簡單的quartz的例子, 來簡單地認識一下它.
使用cron表達式來讓quartz每10秒鍾執行一個任務:
先引入maven依賴:
<!-- https://mvnrepository.com/artifact/org.quartz-scheduler/quartz --> <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.3.2</version> </dependency>
編寫代碼:
import com.alibaba.fastjson.JSON; import org.quartz.*; import org.quartz.impl.StdSchedulerFactory; import static org.quartz.CronScheduleBuilder.cronSchedule; import static org.quartz.JobBuilder.newJob; import static org.quartz.TriggerBuilder.newTrigger; public class QuartzTest implements Job { @Override public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { System.out.println("這里是你的定時任務: " + JSON.toJSONString( jobExecutionContext.getJobDetail())); } public static void main(String[] args) { try { // 獲取到一個StdScheduler, StdScheduler其實是QuartzScheduler的一個代理 Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler(); // 啟動Scheduler scheduler.start(); // 新建一個Job, 指定執行類是QuartzTest(需實現Job), 指定一個K/V類型的數據, 指定job的name和group JobDetail job = newJob(QuartzTest.class) .usingJobData("jobData", "test") .withIdentity("myJob", "group1") .build(); // 新建一個Trigger, 表示JobDetail的調度計划, 這里的cron表達式是 每10秒執行一次 Trigger trigger = newTrigger() .withIdentity("myTrigger", "group1") .startNow() .withSchedule(cronSchedule("0/10 * * * * ?")) .build(); // 讓scheduler開始調度這個job, 按trigger指定的計划 scheduler.scheduleJob(job, trigger); // 保持進程不被銷毀 // scheduler.shutdown(); Thread.sleep(10000000); } catch (SchedulerException se) { se.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } }
上面這個簡單的例子已經包含了quartz的幾個核心組件:
Scheduler - 可以理解為是一個調度的實例,用來調度任務 Job - 這個是一個接口, 表示調度要執行的任務. 類似TimerTask. JobDetail - 用於定義作業的實例。進一步封裝和拓展Job的具體實例 Trigger(即觸發器) - 定義JobDetail的調度計划。例如多久執行一次, 什么時候執行, 以什么頻率執行等等 JobBuilder - 用於定義/構建JobDetail實例。 TriggerBuilder - 用於定義/構建觸發器實例。
1. Scheduler
Scheduler是一個接口, 它一共有4個實現:
JBoss4RMIRemoteMBeanScheduler
RemoteMBeanScheduler
RemoteScheduler
StdScheduler
我們上面的例子使用的是StdScheduler, 表示的直接在本地進行調度(其他的都帶有remote字樣, 明顯是跟遠程調用有關).
來看一下StdScheduler的注釋和構造方法
/** * <p> * An implementation of the <code>Scheduler</code> interface that directly * proxies all method calls to the equivalent call on a given <code>QuartzScheduler</code> * instance. * </p> * * @see org.quartz.Scheduler * @see org.quartz.core.QuartzScheduler * * @author James House */ public class StdScheduler implements Scheduler { /** * <p> * Construct a <code>StdScheduler</code> instance to proxy the given * <code>QuartzScheduler</code> instance, and with the given <code>SchedulingContext</code>. * </p> */ public StdScheduler(QuartzScheduler sched) { this.sched = sched; } }
原來StdScheduler只不過是一個代理而已, 它最終都是調用org.quartz.core.QuartzScheduler類的方法.
查看RemoteScheduler等另外三個的實現, 也都是代理QuartzScheduler而已.
所以很明顯, quartz的核心是QuartzScheduler類.
所以來看一下QuartzScheduler的javadoc注釋:
/** * <p> * This is the heart of Quartz, an indirect implementation of the <code>{@link org.quartz.Scheduler}</code> * interface, containing methods to schedule <code>{@link org.quartz.Job}</code>s, * register <code>{@link org.quartz.JobListener}</code> instances, etc. * </p> * * @see org.quartz.Scheduler * @see org.quartz.core.QuartzSchedulerThread * @see org.quartz.spi.JobStore * @see org.quartz.spi.ThreadPool * * @author James House */ public class QuartzScheduler implements RemotableQuartzScheduler { ... }
大概意思就是說: QuartzScheduler是quartz的心臟, 間接實現了org.quartz.Scheduler接口, 包含了調度Job和注冊JobListener的方法等等
說是間接實現說Scheduler接口,但是來看一下它的繼承圖, 你會發現它跟Scheduler接口沒有半毛錢關系(果然夠間接的), 完全是自己獨立搞了一套, 基本所有調度相關的邏輯都在里面實現了

另外從這個繼承圖中的RemotableQuartzScheduler也可以看出, QuartzScheduler是天生就可以支持遠程調度的(通過rmi遠程觸發調度, 調度的管理和調度的執行可以分離).
當然, 實際應用中也大多數都是這么用, 只是我們這個最簡單的例子是本地觸發調度,本地執行任務而已.
2. Job, JobDetail
Job是一個接口, 它只定義了一個execute方法, 代表任務執行的邏輯.
public interface Job { void execute(JobExecutionContext context) throws JobExecutionException; }
JobDetail其實也是一個接口, 它的默認實現是JobDetailImpl.JobDetail內部指定了JobDetail的實現類, 另外還新增了一些參數:
1. name和group, 會組合成一個JobKey對象, 作為這個JobDetail的唯一標識ID 2. jobDataMap, 可以給Job傳遞一些額外參數 3. durability, 是否需要持久化.這就是quartz跟一般的Timer之流不一樣的地方了. 他的job是可以持久化到數據庫的
可以看的出來, JobDetail其實是對Job類的一種增強. Job用來表示任務的執行邏輯, 而JobDetail更多的是跟Job管理相關.
3. Trigger
Trigger接口可以說才是quartz的核心功能. 因為quartz是一個定時任務調度框架, 而定時任務的調度邏輯, 就是在Trigger中實現的.
來看一下Trigger的實現類, 乍一看還挺多. 但是實際就圖中紅圈圈出來的那幾個是真正的實現類, 其他的都是接口或實現類:

而實際上, 我們用得最多的也只是SimpleTriggerImpl和CronTriggerImpl, 前者表示簡單的調度邏輯,例如每1分鍾執行一次. 后者可以使用cron表達式來 指定更復雜的調度邏輯.
很明顯, 上面簡單的例子我們用的是CronTriggerImp
不過需要注意的是, quartz的cron表達式和linux下crontab的cron表達式是有一定區別的, 它可以直接到秒級別:
1. Seconds 2. Minutes 3. Hours 4. Day-of-Month 5. Month 6. Day-of-Week 7. Year (optional field) 例如: "0 0 12?* WED" - 這意味着"每個星期三下午12:00"。
使用CronTrigger的時候, 直接寫cron表達式是比較容易出錯的, 所以最好有個工具驗證一下自己的cron表達式是否寫正確, 以及驗證觸發的時間是否是我們期待的.
這個工作已經有人幫我們做好了, 例如下面這個網站:
實際效果如下:

以上就算是quartz的一個入門教程了. 但是確實也只是一個入門教程而已.實際上quartz遠比這個例子表現出來的復雜, 也同時也遠比這個例子體現出來的強大.
例如:
1. quartz可以配置成集群模式,可以提供失敗轉移,負載均衡等功能, 在提升計算能力的同時,也提升了系統的可用性 2. quartz還支持JTA事務, 可以將一些job運行在一個事務中 3. 只要服務器資源上能支持, quartz理論上能運行成千上萬的job 4. 等等等...
當然, quartz也不是沒有缺點; 整個框架的重點都是在於"調度"上,而忽略了一些其他的方面, 例如交互和性能.
-
交互上, quartz只是提供了"scheduler.scheduleJob(job, trigger)" 這種api的方式. 沒有提供任何的管理界面,這是非常的不人性化的.
-
quartz並沒有原生地支持分片的功能.這會導致運行一個大的任務時, 運行時間會非常的長. 例如要跑一億個會員的數據時, 有可能一天都跑不完.如果是支持分片的那就好辦很多了.可以把一億會員拆分到多個實例上跑, 性能更高.
在這兩點上, 一些其他的框架做得就更好了.