分布式定時任務調(diào)度框架選型
點擊上方“程序員大白”,選擇“星標”公眾號
重磅干貨,第一時間送達

背景
業(yè)務場景
定期執(zhí)行任務:如每天0點做資源稽查;
需求和痛點
集群部署服務時,如何確保任務不被重復執(zhí)行?---最急迫
如何監(jiān)控、告警等;
高可用、無單點故障;
優(yōu)秀的并行處理能力、分片能力;
自研 or 開源
任何工具的使用都要結(jié)合自身的業(yè)務場景,脫落業(yè)務場景談技術(shù)選型就是耍流氓。
考慮私有云場景業(yè)務量一般,高并發(fā)場景很少遇到,同一時間也不會有超大量定時任務同時需要執(zhí)行,所以考慮自研也未嘗不可。
目前自研最急需解決的問題并不是高并發(fā),而是如何避免任務被重復執(zhí)行;
場景就變成了:
多個應用在同一個時間都嘗試去執(zhí)行任務,但最終只有一個應用真正執(zhí)行。
這樣的話,立馬就會讓人聯(lián)想到使用鎖去解決,因為是多個應用,所以就是分布式鎖。那么,場景又變了:
多個應用在同一個時間都嘗試去獲取分布式鎖,只有一個應用能搶到這把鎖,搶到鎖的應用可以執(zhí)行定時任務,其他應用則直接放棄,等待下一次執(zhí)行時間。
搶鎖的時機是每次定時任務執(zhí)行之前,這又讓我聯(lián)想到了AOP,那么利用注解也就順理成章了。
下面的 ShedLock就是基于AOP + 注解的思想。
ShedLock
與Spring的集成挺方便;
Distributed lock for your scheduled tasks
Github: start: 1K Fork: 192,最近一次代碼提交 6months ago
ShedLock makes sure that your scheduled tasks are executed at most once at the same time. If a task is being executed on one node, it acquires a lock which prevents execution of the same task from another node (or thread). Please note, that if one task is already being executed on one node, execution on other nodes does not wait, it is simply skipped.
ShedLock uses external store like Mongo, JDBC database, Redis, Hazelcast, ZooKeeper or others for coordination.
ShedLock is not a distributed scheduler
Please note that ShedLock is not and will never be full-fledged scheduler, it's just a lock. If you need a distributed scheduler, please use another project. ShedLock is designed to be used in situations where you have scheduled tasks that are not ready to be executed in parallel, but can be safely executed repeatedly.
重要信息:
ShedLock可使用MongoDB、JDBC-DB、Redis或Zookeeper等來實現(xiàn)分布式鎖,具體采用哪種方式,由使用者決定;
它僅僅是一個分布式鎖,并不是調(diào)度程序;
與Spring的集成很簡單(官方文檔的示例,自己并未測試過),示例:
假設一個task每15分鐘執(zhí)行一次,每次運行的時間都不是很長(即:應該在15min分鐘內(nèi)可以運行完),更重要的是,不管起多少個實例,都只希望在15分鐘內(nèi)該任務有且只被運行一次!
//示例:與Spring的原生注解 @Scheduled配合使用
import net.javacrumbs.shedlock.core.SchedulerLock;
@Scheduled(cron = "0 */15 * * * *")//每15分鐘運行一次
@SchedulerLock(name = "scheduledTaskName", lockAtMostFor = "14m", lockAtLeastFor = "14m")
public void scheduledTask() {
// do something
}
//lockAtMostFor : 表示最多鎖定14分鐘,主要用于防止執(zhí)行任務的節(jié)點掛掉(即使這個節(jié)點掛掉,在14分鐘后,鎖也被釋放),一般將其設置為明顯大于任務的最大執(zhí)行時長;如果任務運行時間超過該值(即任務14分鐘沒有執(zhí)行完),則該任務可能被重復執(zhí)行!
//lockAtLeastFor : 至少鎖定時長,確保在15分鐘內(nèi),該任務不會運行超過1次;額外補充一句,關(guān)于 @Scheduled的一些不注意的細節(jié)(單線程執(zhí)行),需要特別注意:SpringTask實現(xiàn)定時任務
Configure LockProvider
使用JdbcTemplete來實現(xiàn)分布式鎖
CREATE TABLE shedlock(
name VARCHAR(64),
lock_until TIMESTAMP(3) NULL,
locked_at TIMESTAMP(3) NULL,
locked_by VARCHAR(255),
PRIMARY KEY (name)
)依賴
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-provider-jdbc-template</artifactId>
<version>4.5.1</version>
</dependency>配置
import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider;...
@Bean
public LockProvider lockProvider(DataSource dataSource) {
return new JdbcTemplateLockProvider(dataSource);
}
@Bean
public ScheduledLockConfiguration scheduledLockConfiguration(LockProvider lockProvider) {
return ScheduledLockConfigurationBuilder
.withLockProvider(lockProvider)
.withPoolSize(10)
.withDefaultLockAtMostFor(Duration.ofMinutes(10))
.build();
}
基于Piston的思路(異步監(jiān)控),重寫一個
todo
思路:
還是基于Innodb引擎的行鎖;
狀態(tài):應該基于調(diào)度時間去判斷狀態(tài);
選型參考
簡單、易上手;
額外依賴較少,比如是否需要依賴MQ、Zookeeper等;
是否方便集成;
開源產(chǎn)品對比
開源產(chǎn)品:任務調(diào)度系統(tǒng)匯總
Quartz
Java事實上的定時任務標準。但Quartz關(guān)注點在于定時任務而非數(shù)據(jù),并無一套根據(jù)數(shù)據(jù)處理而定制化的流程。雖然Quartz可以基于數(shù)據(jù)庫實現(xiàn)作業(yè)的高可用,但缺少分布式并行調(diào)度的功能
原理

獨立的Quratz節(jié)點之間是不需要通信的,不同節(jié)點之間是通過數(shù)據(jù)庫表來感知另一個應用,只有使用持久的JobStore才能完成Quartz集群。如果某一個節(jié)點失效,那么Job會在其他節(jié)點上執(zhí)行。
如何保證只在一臺機器上觸發(fā)?
數(shù)據(jù)庫悲觀鎖
一旦某一節(jié)點線程獲取了該鎖,那么Job就會在這臺機器上被執(zhí)行,其他節(jié)點進行鎖等待;
缺點
不適合大量的短任務 & 不適合過多節(jié)點部署;
解決了高可用的問題,并沒有解決任務分片的問題,存在單機處理的極限(即:不能實現(xiàn)水平擴展)。
需要把任務信息持久化到業(yè)務數(shù)據(jù)表,和業(yè)務有耦合
調(diào)度邏輯和執(zhí)行邏輯并存于同一個項目中,在機器性能固定的情況下,業(yè)務和調(diào)度之間不可避免地會相互影響。
quartz集群模式下,是通過數(shù)據(jù)庫獨占鎖來唯一獲取任務,任務執(zhí)行并沒有實現(xiàn)完善的負載均衡機制。
Elastic-Job
官方文檔

GitHub: star 5.7K Fork:2.6K
Elastic-Job is a distributed scheduled job framework, based on Quartz and Zookeeper
Elastic Job是當當網(wǎng)架構(gòu)師開發(fā),是一個分布式調(diào)度解決方案,由兩個相互獨立的子項目Elastic-Job-Lite和Elastic-Job-Cloud組成;定位為輕量級無中心化解決方案,使用 jar 包的形式提供分布式任務的協(xié)調(diào)服務。支持分布式調(diào)度協(xié)調(diào)、彈性擴容縮容、失效轉(zhuǎn)移、錯過執(zhí)行作業(yè)重觸發(fā)、并行調(diào)度、自診斷和修復等等功能特性。
Elastic-Job-Lite定位為輕量級無中心化解決方案,使用jar包的形式提供分布式任務的協(xié)調(diào)服務;Elastic-Job-Cloud采用自研Mesos Framework的解決方案,額外提供資源治理、應用分發(fā)以及進程隔離等功能;
Elastic-Job-Lite并沒有宿主程序,而是基于部署作業(yè)框架的程序在到達相應時間點時各自觸發(fā)調(diào)度。它的開發(fā)也比較簡單,引用Jar包實現(xiàn)一些方法即可,最后編譯成Jar包運行。Elastic-Job-Lite的分布式部署全靠ZooKeeper來同步狀態(tài)和原數(shù)據(jù)。實現(xiàn)高可用的任務只需將分片總數(shù)設置為1,并把開發(fā)的Jar包部署于多個服務器上執(zhí)行,任務將會以1主N從的方式執(zhí)行。一旦本次執(zhí)行任務的服務器崩潰,其他執(zhí)行任務的服務器將會在下次作業(yè)啟動時選擇一個替補執(zhí)行。如果開啟了失效轉(zhuǎn)移,那么功能效果更好,可以保證在本次作業(yè)執(zhí)行時崩潰,備機之一立即啟動替補執(zhí)行。
Elastic-Job-Lite的任務分片也是通過ZooKeeper來實現(xiàn),Elastic-Job并不直接提供數(shù)據(jù)處理的功能,框架只會將分片項分配至各個運行中的作業(yè)服務器,開發(fā)者需要自行處理分片項與真實數(shù)據(jù)的對應關(guān)系??蚣芤差A置了一些分片策略:平均分配算法策略,作業(yè)名哈希值奇偶數(shù)算法策略,輪轉(zhuǎn)分片策略。同時也提供了自定義分片策略的接口。
另外Elastic-Job-Lite還提供了一個任務監(jiān)控和管理界面:Elastic-Job-Lite-Console。它和Elastic-Job-Lite是兩個完全不關(guān)聯(lián)的應用程序,使用ZooKeeper來交換數(shù)據(jù),管理人員可以通過這個界面查看、監(jiān)控和管理Elastic-Job-Lite的任務,必要的時候還能手動觸發(fā)任務
功能列表
分布式調(diào)度協(xié)調(diào)
彈性擴容縮容
失效轉(zhuǎn)移
錯過執(zhí)行作業(yè)重觸發(fā)
作業(yè)分片一致性,保證同一分片在分布式環(huán)境中僅一個執(zhí)行實例
自診斷并修復分布式不穩(wěn)定造成的問題
支持并行調(diào)度
支持作業(yè)生命周期操作
豐富的作業(yè)類型
Spring整合以及命名空間提供
運維平臺
優(yōu)缺點
優(yōu)點:
基于成熟的定時任務作業(yè)框架Quartz cron表達式執(zhí)行定時任務;
支持任務分片:可以拆分任務,分別由不同節(jié)點執(zhí)行;
官網(wǎng)文檔齊全,全中文;
彈性擴容縮容:運行中的作業(yè)服務器崩潰,或新增N臺作業(yè)服務器,作業(yè)框架將在下次作業(yè)執(zhí)行前重新分片,不影響當前作業(yè)執(zhí)行;
任務監(jiān)控和管理界面;

缺點:
依賴Zookeeper;
XXL-JOB
官方文檔
GitHub: star: 12.9K Fork:5.5K
使用該框架的公司:>300家
XXL-Job官網(wǎng)是大眾點評員工徐雪里于2015年發(fā)布的分布式任務調(diào)度平臺,其核心設計目標是開發(fā)迅速、學習簡單、輕量級、易擴展?,F(xiàn)已開放源代碼并接入多家公司線上產(chǎn)品線,開箱即用。
設計思想
將調(diào)度行為抽象形成“調(diào)度中心”公共平臺,而平臺自身并不承擔業(yè)務邏輯,“調(diào)度中心”負責發(fā)起調(diào)度請求。
將任務抽象成分散的JobHandler,交由“執(zhí)行器”統(tǒng)一管理,“執(zhí)行器”負責接收調(diào)度請求并執(zhí)行對應的JobHandler中業(yè)務邏輯。
因此,“調(diào)度”和“任務”兩部分可以相互解耦,提高系統(tǒng)整體穩(wěn)定性和擴展性;


系統(tǒng)組成
調(diào)度模塊(調(diào)度中心):
負責管理調(diào)度信息,按照調(diào)度配置發(fā)出調(diào)度請求,自身不承擔業(yè)務代碼。調(diào)度系統(tǒng)與任務解耦,提高了系統(tǒng)可用性和穩(wěn)定性,同時調(diào)度系統(tǒng)性能不再受限于任務模塊;
支持可視化、簡單且動態(tài)的管理調(diào)度信息,包括任務新建,更新,刪除,GLUE開發(fā)和任務報警等,所有上述操作都會實時生效,同時支持監(jiān)控調(diào)度結(jié)果以及執(zhí)行日志,支持執(zhí)行器Failover。
執(zhí)行模塊(執(zhí)行器):
負責接收調(diào)度請求并執(zhí)行任務邏輯。任務模塊專注于任務的執(zhí)行等操作,開發(fā)和維護更加簡單和高效;
接收“調(diào)度中心”的執(zhí)行請求、終止請求和日志請求等。
其他
uncode-schedule
基于zookeeper,比較小眾,不推薦
基于zookeeper+spring task/quartz的分布式任務調(diào)度組件,確保所有任務在集群中不重復,不遺漏的執(zhí)行。支持動態(tài)添加和刪除任務。
LTS
最近一次更新在2年前,目前不是很活躍;也比較小眾,不推薦
light-task-scheduler
LTS(light-task-scheduler)主要用于解決分布式任務調(diào)度問題,支持實時任務,定時任務和Cron任務。有較好的伸縮性,擴展性;
TBSchedule
阿里早期開源的分布式任務調(diào)度系統(tǒng)。代碼略陳舊,使用timer而非線程池執(zhí)行任務調(diào)度。眾所周知,timer在處理異常狀況時是有缺陷的。而且TBSchedule作業(yè)類型較為單一,只能是獲取/處理數(shù)據(jù)一種模式。還有就是文檔缺失比較嚴重。
依賴Zookeeper;
代碼太久沒更新
TBSchedule是一款非常優(yōu)秀的高性能分布式調(diào)度框架,廣泛應用于阿里巴巴、淘寶、支付寶、京東、聚美、汽車之家、國美等很多互聯(lián)網(wǎng)企業(yè)的流程調(diào)度系統(tǒng)。tbschedule在時間調(diào)度方面雖然沒有quartz強大,但是它支持分片功能。和quartz不同的是,tbschedule使用ZooKeeper來實現(xiàn)任務調(diào)度的高可用和分片。
TBSchedule的分布式機制是通過靈活的Sharding方式實現(xiàn)的,分片的規(guī)則由客戶端決定,比如可以按所有數(shù)據(jù)的ID按10取模分片、按月份分片等等。TBSchedule的宿主服務器可以進行動態(tài)擴容和資源回收,這個特點主要是因為它后端依賴的ZooKeeper,這里的ZooKeeper對于TBSchedule來說是一個NoSQL,用于存儲策略、任務、心跳信息數(shù)據(jù),它的數(shù)據(jù)結(jié)構(gòu)類似文件系統(tǒng)的目錄結(jié)構(gòu),它的節(jié)點有臨時節(jié)點、持久節(jié)點之分。調(diào)度引擎啟動后,隨著業(yè)務量數(shù)據(jù)量的增多,當前Cluster可能不能滿足目前的處理需求,那么就需要增加服務器數(shù)量,一個新的服務器上線后會在ZooKeeper中創(chuàng)建一個代表當前服務器的一個唯一性路徑(臨時節(jié)點),并且新上線的服務器會和ZooKeeper保持長連接,當通信斷開后,節(jié)點會自動摘除。
TBSchedule會定時掃描當前服務器的數(shù)量,重新進行任務分配。TBSchedule不僅提供了服務端的高性能調(diào)度服務,還提供了一個scheduleConsole的war包,隨著宿主應用的部署直接部署到服務器,可以通過web的方式對調(diào)度的任務、策略進行監(jiān)控管理,以及實時更新調(diào)整。
Saturn
Saturn是唯品會在github開源的一款分布式任務調(diào)度產(chǎn)品。它是基于當當elastic-job 1.0版本來開發(fā)的,其上完善了一些功能和添加了一些新的feature。
亮點:
支持多語言開發(fā) python、Go、Shell、Java、Php。
管理控制臺和數(shù)據(jù)統(tǒng)計分析更加完善
缺點:
技術(shù)文檔較少 , 該框架是2016年由唯品會的研發(fā)團隊基于elastic-job開發(fā)而來
Opencron
比較小眾,不推薦
Antares
Antares 是一款基于 Quartz 機制的分布式任務調(diào)度管理平臺,內(nèi)部重寫執(zhí)行邏輯,一個任務僅會被服務器集群中的某個節(jié)點調(diào)度。用戶可通過對任務預分片,有效提升任務執(zhí)行效率;也可通過控制臺 antares-tower 對任務進行基本操作,如觸發(fā),暫停,監(jiān)控等。
Antares 是基于 Quartz 的分布式調(diào)度,支持分片,支持樹形任務依賴,但是不是跨平臺的
sia-task
沒有仔細研究,依賴Zookeeper,感覺更偏向于解決編排和跨平臺
無論是互聯(lián)網(wǎng)應用或者企業(yè)級應用,都充斥著大量的批處理任務。我們常常需要一些任務調(diào)度系統(tǒng)幫助我們解決問題。隨著微服務化架構(gòu)的逐步演進,單體架構(gòu)逐漸演變?yōu)榉植际?、微服務架?gòu)。在此的背景下,很多原先的任務調(diào)度平臺已經(jīng)不能滿足業(yè)務系統(tǒng)的需求。于是出現(xiàn)了一些基于分布式的任務調(diào)度平臺。這些平臺各有其特點,但各有不足之處,比如不支持任務編排、與業(yè)務高耦合、不支持跨平臺等問題。不是非常符合公司的需求,因此我們開發(fā)了微服務任務調(diào)度平臺(SIA-TASK)。
SIA是我們公司基礎開發(fā)平臺Simple is Awesome的簡稱,SIA-TASK(微服務任務調(diào)度平臺)是其中的一項重要產(chǎn)品,SIA-TASK契合當前微服務架構(gòu)模式,具有跨平臺,可編排,高可用,無侵入,一致性,異步并行,動態(tài)擴展,實時監(jiān)控等特點。
對比和結(jié)論



XXL-Job 側(cè)重的業(yè)務實現(xiàn)的簡單和管理的方便,學習成本簡單,失敗策略和路由策略豐富。推薦使用在“用戶基數(shù)相對少,服務器數(shù)量在一定范圍內(nèi)”的情景下使用
Elastic-Job 關(guān)注的是數(shù)據(jù),增加了彈性擴容和數(shù)據(jù)分片的思路,以便于更大限度的利用分布式服務器的資源。但是學習成本相對高些,推薦在“數(shù)據(jù)量龐大,且部署服務器數(shù)量較多”時使用
對于并發(fā)場景不是特別高的系統(tǒng)來說,xxl-job配置部署簡單易用,不需要引入多余的組件,同時提供了可視化的控制臺,使用起來非常友好,是一個比較好的選擇。
Elastic-Job
這個框架大概在2年前很火,當時使用的公司很多,想必很多人也聽過了,但是很可惜現(xiàn)在已經(jīng)不在維護了,代碼已經(jīng)有2年沒有更新了,這里違反了更新頻率的原則,如果出現(xiàn)問題可能都沒什么人幫助你,所以并不是很推薦使用。
更傾向于選擇XXL-JOB:
輕量級,支持通過Web頁面對任務進行動態(tài)CRUD操作,操作簡單
只依賴數(shù)據(jù)庫作為集群注冊中心,接入開發(fā)簡單,不需要ZK
高可用、解耦、高性能、監(jiān)控報警、分片、重試、故障轉(zhuǎn)移
團隊持續(xù)開發(fā),社區(qū)活躍
支持后臺直接查看每個任務執(zhí)行實時日志,ELASTIC-JOB中應該是沒有這個功能
ShedLock集成更簡單,如果驗證通過,也未必不是一個好選擇!
其他
中心化調(diào)度 vs 去中心化調(diào)度
中心化 | 去中心化 | |
實現(xiàn)難度 | 高 | 低 |
部署難度 | 高 | 低 |
觸發(fā)時間統(tǒng)一控制 | 可以 | 不可以 |
觸發(fā)延遲 | 有 | 無 |
異構(gòu)語言支持 | 容易 | 不容易 |
中心化調(diào)度
XXL-Job:有調(diào)度中心;
去中心化調(diào)度
Quartz
Elastic-Job:實現(xiàn)時,就是基于Quartz的基于數(shù)據(jù)庫的分布式思想;
參考
XXL-JOB 和 Elastic-Job對比
分布式定時任務調(diào)度框架實踐
source:https://www.yuque.com/ssslinppp/blogs/bvlz5y
推薦閱讀
關(guān)于程序員大白
程序員大白是一群哈工大,東北大學,西湖大學和上海交通大學的碩士博士運營維護的號,大家樂于分享高質(zhì)量文章,喜歡總結(jié)知識,歡迎關(guān)注[程序員大白],大家一起學習進步!
