先說一下為什么要定時任務:
- 數(shù)據(jù)備份搔耕。
- 下單一定時間未支付則取消
- 釘釘定時發(fā)送日志
- 博客定時發(fā)送文章
- app定時推送消息
這些情況其實都是需要定時任務來完成的援雇。但是其實java中定時任務的實現(xiàn)是有多種多樣的濒憋,下面我們一一細說多艇。
單機定時任務技術(shù)選型
Timer
這個是JDK1.3就開始支持的一種定時任務的實現(xiàn)方式鲫懒。當然了反正我工作以來也沒用過這個遭居。其內(nèi)部是使用一個叫做TaskQueue的類來存放定時任務÷曜罚基于最小堆實現(xiàn)的優(yōu)先級隊列税课。按照任務下一次執(zhí)行時間的前后將任務排序。保證堆頂?shù)娜蝿兆钕葓?zhí)行豹缀。這樣需要執(zhí)行任務的時候每次只要取出堆頂?shù)娜蝿者\行即可伯复。
使用起來也比較簡單,如下的demo代碼:
public static void main(String[] args) {
TimerTask timerTask1 = new TimerTask() {
@Override
public void run() {
try {
Thread.sleep(2000l);
}catch (Exception e){
}
System.out.println(Thread.currentThread().getName()+":"+DateUtil.getDate(new Date()));
}
};
TimerTask timerTask2 = new TimerTask() {
@Override
public void run() {
try {
Thread.sleep(2000l);
}catch (Exception e){
}
System.out.println(Thread.currentThread().getName()+":"+DateUtil.getDate(new Date()));
}
};
Timer timer = new Timer();
System.out.println(Thread.currentThread().getName()+":"+DateUtil.getDate(new Date()));
timer.schedule(timerTask1,1000l);
timer.schedule(timerTask2,1000l);
}
我先截圖展示下上面代碼的運行結(jié)果:
其實從運行結(jié)果上我們能看到一些東西:
- 所有的任務都是在一個線程中執(zhí)行的邢笙。而且我明明兩個任務應該同時執(zhí)行啸如,但是結(jié)果是串行執(zhí)行的。這樣任務多了肯定會影響性能
-
還有一點是任務如果異常停止氮惯,那么之后的所有任務都不會執(zhí)行了叮雳。下面是代碼。
timer異常則后面都不執(zhí)行
不過在Timer的注解上有一段注釋妇汗,提到了ScheduledThreadPoolExecutor 支持多線程執(zhí)行定時任務并且功能更強大帘不,可以替代Timer
image.png
ScheduledExecutorService
ScheduledExecutorService 是一個接口,有多個實現(xiàn)類杨箭,其中比較常用的就是上面Timer注釋中推薦使用的ScheduledThreadPoolExecutor寞焙。
ScheduledThreadPoolExecutor本身就是一個線程池,支持并發(fā)執(zhí)行任務互婿。并且其內(nèi)部是DelayQueue延遲隊列 作為任務隊列捣郊。感覺就是Timer的多線程版本,其本質(zhì)上還是指定延遲的時間慈参,而不是cron表達式呛牲,不太容易滿足我們現(xiàn)在的需求的。所以這個怎么使用就不說了驮配。
Spring Task
這個應該算是被spring包裝的最無腦的一個工具了娘扩。啟動類加個注解開啟定時任務@EnableScheduling着茸。 只要確定類被spring管理,然后在方法上加個@Scheduled注解指定時間琐旁。就可以在指定的時間運行此方法涮阔。
因為現(xiàn)在大多數(shù)項目本身就是spring項目,所以這里直接演示:
其實支持cron表達式相比于上面兩個就算是史詩級進步了旋膳。但是目前springTask只支持單機澎语,并且其錯誤重試機制也不是很好(比如該執(zhí)行任務的那個時間服務重啟,那么這次任務就不會執(zhí)行).當然了Spring Task底層據(jù)說是基于ScheduledThreadPoolExecutor 線程池來實現(xiàn)的验懊。
其優(yōu)點是簡單方便擅羞,支持cron表達式
缺點是功能單一,重試機制不好义图。
時間輪
kafka减俏,dubbo,zookeeper碱工,netty等都有對時間輪的實現(xiàn)娃承。
簡單來說就是一個環(huán)形的隊列(底層基于數(shù)組實現(xiàn)),隊列中每一個元素(時間格)都可以存放一個定時任務列表。
時間輪中的每個時間格都代表了時間輪的基本時間跨度或者說時間精度怕篷,假如時間一秒走一個時間格的話历筝,那么這個時間輪的最高精度就是1s。
簡單理解下:我們比如把時間輪的格子設(shè)置成60廊谓,時間精度是1s梳猪。如果接下來我們要新建一個3s后執(zhí)行的定時任務。那么我們把定時任務放在下標為3的格子里就行蒸痹。如果新建20s后的任務春弥,那么把它放在下標是20的格子里。如果我們新建了一個100s后執(zhí)行的任務叠荠。因為這個時間輪只有60個格子匿沛,所以要引入一個叫做圈數(shù)/輪數(shù)的概念。也就是說這個會放在下標是40的格子里榛鼎。但是它的圈數(shù)是2.
當然了這種做法還是有很大的問題逃呼。如果時間精度很低,那么單圈的時間長度會很小者娱。比如說我上面說一圈60s蜘渣。那么表示幾天,幾個月那就圈數(shù)太多了肺然。但是時間精度太大的話,又會導致一個格子里的任務過多腿准。所以就有了升級款多層次時間輪际起。kafka就是采用這種方案的拾碌。
所謂的多層次時間輪有點類似鐘表的設(shè)計。比如說時針表示小時街望,分針表示分鐘校翔,秒針表示秒。這就是三層的時間輪灾前。
我們想判斷一個時間點防症,比如我們想看八點二十六分二十一秒。會先去看時針哎甲,如果時針在8到9之間蔫敲,那么會繼續(xù)去看分針,是不是在5-6之間炭玫。如果滿足這個條件我們才會取看秒針奈嘿,是不是在4-5的位置。
二多層次時間輪也是類似的功能吞加。只不過是指針只有一個裙犹,在多個層之間升降機。
比如說三個層級就按照鐘表來設(shè)置精度和格子位(時是12個格子衔憨。分60叶圃,秒60)。我們現(xiàn)在想設(shè)置一個4小時2分鐘21秒后執(zhí)行的任務践图。
那么我們會把這個任務添加到第三層(精度是小時那一層) 的下標是4的位置掺冠。
當指針真的來到了這個位置,第三層的第4個格子的任務會被移動到第二層平项。然后到了第二層的第2個格子上赫舒,第二層第2個格子的任務繼續(xù)移動到第一層。最終移動到第一層的第21個格子完成闽瓢。
時間輪比較適合任務數(shù)量比較多的定時任務場景接癌,它的任務寫入和執(zhí)行的時間復雜度都是O(1).
分布式定時任務技術(shù)選型
上面提到的一些定時任務的解決方案都是單機下執(zhí)行的,適用于單體項目扣讼。如果我們需要一些高級特性缺猛,比如支持分布式場景下的分片和高可用的話,我們就需要用到分布式任務調(diào)度框架了椭符。
通常情況下荔燎,一個定時任務的執(zhí)行會有三個角色:
- 任務:要執(zhí)行的具體任務。比如發(fā)日志
- 調(diào)度器:也叫調(diào)度中心销钝,主要負責任務管理有咨,會分配任務給執(zhí)行器。
- 執(zhí)行器:接收調(diào)度器分派的任務并執(zhí)行蒸健。
Quartz
這個是一個很火的開源任務調(diào)度框架座享,完全由java寫成婉商。這個也是我接觸的第一個任務調(diào)度框架≡眩可以說是java定時任務領(lǐng)域的參考標準丈秩,其余的任務調(diào)度框架基本上都是基于quartz開發(fā)的。比如elastic-job就是基于quartz二次開發(fā)之后的分布式調(diào)度解決方案淳衙。
使用quartz可以很方便的與spring集成蘑秽。并且支持動態(tài)添加任務和集群。但是quartz使用起來有點麻煩箫攀,api繁瑣肠牲。
而且quartz為了防止系統(tǒng)宕機任務丟失等意外,是支持把任務維護到數(shù)據(jù)庫中的匠童。但是是通過數(shù)據(jù)庫的鎖機制做的埂材,系統(tǒng)侵入性嚴重,節(jié)點負載不均衡汤求,有點偽分布式的味道俏险。
- 優(yōu)點:可以與spring 集成,支持動態(tài)添加任務和集群扬绪。
- 缺點:分布式支持不友好竖独,沒有ui管理平臺,相比于其它同類型的框架使用更麻煩挤牛。
Elastic-job
上面有說過這個莹痢,當當網(wǎng)基于Quartz和zookeeper的分布式調(diào)度解決方案。由兩個相互獨立的子項目Elastic-job-lite和Elastic-job-cloud組成墓赴。一般我們使用前者就好(這個其實我沒在工作中用過)竞膳。
ElasticJob支持任務在分布式場景下的分片和高可用,任務可視化管理等功能诫硕。
從上圖可以看出Elastic-Job沒有調(diào)度中心的概念坦辟。是用zookeeper作為注冊中心,負責協(xié)調(diào)分配任務到不同的節(jié)點上章办。
Elastic-Job中的定時調(diào)度都是由執(zhí)行器自行觸發(fā)锉走,這種設(shè)計也被稱為去中心化設(shè)計(調(diào)度和處理都是執(zhí)行器單獨完成)。
下面是使用demo:
@Component
@ElasticJobConf(name = "dayJob", cron = "0/10 * * * * ?", shardingTotalCount = 2,
shardingItemParameters = "0=AAAA,1=BBBB", description = "簡單任務", failover = true)
public class TestJob implements SimpleJob {
@Override
public void execute(ShardingContext shardingContext) {
log.info("TestJob任務名:【{}】, 片數(shù):【{}】, param=【{}】", shardingContext.getJobName(), shardingContext.getShardingTotalCount(),
shardingContext.getShardingParameter());
}
}
附上elastic-job的官方地址:https://shardingsphere.apache.org/elasticjob/index_zh.html
- 優(yōu)點:與spring集成藕届,支持分布式挪蹭,集群,性能不錯休偶。
- 缺點:依賴zookeeper梁厉,復雜度增加,可靠性降低踏兜,維護成本高词顾。
XXL-JOB
這個 是2015年開源的一款優(yōu)秀的輕量級分布式任務調(diào)度框架只冻。支持任務可視化管理,彈性擴容收縮计技,任務失敗重試和告警,任務分片等功能山橄。
從上圖可以看出XXL-JOB由調(diào)度中心和執(zhí)行器組成垮媒。調(diào)度中心負責任務管理,執(zhí)行器管理和日志管理航棱。執(zhí)行器主要接收調(diào)度信號并處理睡雇。另外調(diào)度中心進行任務調(diào)度時,是通過自研RPC來實現(xiàn)的饮醇。
不同于Elastic-job的去中心化設(shè)計它抱,XXL-JOB這種設(shè)計也稱為 中心化設(shè)計。
XXL-JOB也是基于數(shù)據(jù)庫鎖調(diào)度任務朴艰,存在性能瓶頸观蓄。不過一般不是任務量特別大的情況也沒什么影響,我上家公司就是使用XXL-JOB來實現(xiàn)定時任務的祠墅。
實際上我們使用XXL-JOB來實現(xiàn)定時任務很簡單侮穿,只要繼承IJobHandler就行了。
@JobHandler(value="myApiJobHandler")
@Component
public class MyApiJobHandler extends IJobHandler {
@Override
public ReturnT<String> execute(String param) throws Exception {
//......
return ReturnT.SUCCESS;
}
}
也可以直接基于注解定義任務:
@XxlJob("myAnnotationJobHandler")
public ReturnT<String> myAnnotationJobHandler(String param) throws Exception {
//......
return ReturnT.SUCCESS;
}
我個人覺得還是基于注解比較好毁嗦,因為有多個任務的話,第一種方式要創(chuàng)建多個類。而注解的話只需要多個方法征绸。注意這個job的名字要和在xxl-job的平臺上設(shè)置的一樣赖晶。
至于使用也是比較簡單的,我直接附上官方地址(官方非常友好腔长,是我覺得少有的特別喜歡的官方手冊之一袭祟,其余比較喜歡的是mybatis plus的還有vue的):
https://www.xuxueli.com/xxl-job/
這個優(yōu)點是開箱即用,與spring集成饼酿,支持分布式榕酒,集群,內(nèi)置ui管理控制臺故俐。缺點暫時覺得沒有想鹰,非要雞蛋里挑骨頭的話就是任務配置有問題會導致項目起不來?
PowerJob
這個是個新的分布式調(diào)度框架药版。據(jù)說是當時的作者在阿里巴巴實習辑舷,阿里使用的是自研的 SchedulerX(阿里云付費產(chǎn)品)。實習期滿之后槽片,作者離開了阿里何缓,覺得游戲玩夠了肢础,所以打算扛起新一代分布式任務調(diào)度與計算框架的大旗。然后PowerJob就誕生了碌廓。
由于 SchedulerX 屬于人民幣產(chǎn)品传轰,我這里就不過多介紹。PowerJob 官方也對比過其和 QuartZ谷婆、XXL-JOB 以及 SchedulerX慨蛙。
因為這個PowerJob我也沒用過,所以不介紹了纪挎。上面說到的除了PowerJob和Elastic_Job剩下的我都用過期贫。從我用過的技術(shù)中總結(jié)起來如下:
單體的不介意因為系統(tǒng)原因?qū)е氯蝿詹接|發(fā)的,強烈推薦spring task.簡單便捷直接上手异袄,不用五分鐘就能會用通砍。
單體的但是很介意系統(tǒng)原因(執(zhí)行任務的點宕機了)會不執(zhí)行任務的,建議用持久化的定時任務框架烤蜕,建議xxl-job.
分布式項目任務量不大直接xxl-job走起封孙。任務量巨大可以選elastic-job。
單體的我就不說了玖绿,說下quartz敛瓷,我還是好多年前用過,說實話比較繁瑣斑匪,學習成本很大呐籽,不建議使用了。然后elastic-job蚀瘸,我之前為了寫這篇文章也搭建demo了狡蝶,我個人感覺是不如xxl-job用著方便。而且用到了zk贮勃。我是認為我們采用技術(shù)盡量選擇直觀和可控的贪惹。
最后上面介紹了xxl-job是基于數(shù)據(jù)庫鎖,所以有性能瓶頸寂嘉,雖然我還沒經(jīng)歷過定時任務量很大的場景奏瞬,不過如果真有這種需求的話,可以選擇別的分布式定時任務框架泉孩。
本篇筆記就記到這里硼端,如果稍微幫到你了記得點個喜歡點個關(guān)注。也祝大家工作順順利利寓搬,生活愉快珍昨!