前言
在上一篇 xxl-job 執行器原理分析 一文中,我們提到了 xxl-job 架構中包含了兩個核心子產品:排程中心 和 執行器, 其中排程中心主要負責 任務的排程 , 而執行器負責 任務的執行, 兩者各司其職。 緊接着我們通過畫圖的方式對 執行器 的内部構造進行了分析,并且還對 Job 的執行流程進行了梳理。
本文我們繼續圍繞任務的排程流程對 排程中心 進行剖析, 内容依然參照 xx-job v2.x 版本的源碼。
正文
再看一遍 xxl-job 架構圖:
排程中心主要提供了兩個功能: 系統管理 和 任務排程。其餘的都是一些輔助功能。
- 系統管理正如圖中所示的那樣, 包括任務管理、執行器管理、日志管理。還提供了管理界面。
- 任務排程就是負責從資料中心拉取任務,并按照執行時間将任務投遞給執行器。
排程器的組成結構
兩個核心線程
當排程中心啟動後,會啟動以下兩個線程:
- schedulerThread
主要做如下兩件事情:scheudlerThread
- 從資料中心(db),也就是
表中掃描出符合 條件 1 的任務, 條件1 限制如下:xxl_job_info
- 任務執行時間 小于(目前時間 + 5 s)
- 限制掃描個數, 這個值是動态的,會根據後面的提到的 快慢線程池 中線程數量有關系。
count = treadpool-size * trigger-qps (each trigger cost 50ms, qps = 1000/50 = 20) treadpool-size = (getTriggerPoolFastMax() + getTriggerPoolSlowMax()) * 20 // 看完快慢線程池的介紹,再回過頭來看這裡,會更容易了解
- 掃描出來的任務被劃分為以下 3 類:
- ringThread
ringThread
的作用就是不斷從 容器 中讀取 目前時間點需要執行 的任務, 讀取出來的任務會交給一個叫 快慢線程池 的東西去将任務傳遞給排程器去執行。
時間輪
上述的
ringThread
和 容器 共同組成了一個時間輪。
關于時間輪,如果展開來講内容會很多,需要詳細了解的可以參考 這篇文章,或者自行搜尋, 本文隻做簡單了解。
簡單來講,時間輪實作了 延遲執行 的功能,它在 xxl-job 中的作用就是讓 還未到達執行時間 的任務,按照預計的時間通過 快慢線程池 一個一個送到 執行器 中去執行。
- 時間輪的資料結構一般是 數組 + 連結清單, 和 jdk1.7 中的 HashMap 是一個道理,連結清單中的每個節點就是一個待執行的任務。
- xxl-job 中的時間輪可以形象描述為以下這張圖,像一個隻有秒針的鐘表一樣。
- ringThread 線程運作過程中,每秒會掃過一個刻度,假設目前刻度位置存在 job 連結清單,就把連結清單中的所有 job 取出來,最後丢給 快慢線程池。
- 當然 xxl-job 為了避免處理耗時太長,會跨過刻度,多向前校驗一個刻度;也就是當指針指到 2s 時,會把 1s 和 2s 位置的任務同時讀取出來。
快慢線程池
上面提到任務從資料中心掃描出來後,随之就會被丢到快慢線程池中,快慢線程池的定義如下:
fastTriggerPool = new ThreadPoolExecutor(
10,
XxlJobAdminConfig.getAdminConfig().getTriggerPoolFastMax(),
60L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(1000),
new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "xxl-job, admin JobTriggerPoolHelper-fastTriggerPool-" + r.hashCode());
}
});
slowTriggerPool = new ThreadPoolExecutor(
10,
XxlJobAdminConfig.getAdminConfig().getTriggerPoolSlowMax(),
60L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(2000),
new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "xxl-job, admin JobTriggerPoolHelper-slowTriggerPool-" + r.hashCode());
}
});
由上可知, 快慢線程池包含了兩個線程池 fast 和 slow,當一個 job 送出到快慢線程池後,快慢線程池會根據一些條件, 選擇其中一個線程池去執行後續的操作。
快慢線程池的作用如下:
實作線程池隔離:排程線程池進行隔離拆分,慢任務自動降級進入”Slow”線程池,避免耗盡排程線程,提高系統穩定性;
什麼是慢任務?
如果一個任務在 1 分鐘内,它的執行逾時次數超過 10 次,就被歸為 慢任務
當具體的快或者慢線程池接收到排程任務時,會通過 RPC 遠端調用去觸發 執行器 完成任務的執行邏輯。
當執行器接收到排程任務時,具體是如何執行任務的,可以參考 xxl-job 執行器原理分析 一文。
源碼入口
com.xxl.job.admin.core.thread.JobScheduleHelper#start
com.xxl.job.admin.core.thread.JobTriggerPoolHelper#addTrigger
com.xxl.job.core.biz.client.ExecutorBizClient#run
總結
本文基于 xxl-job v2.x 的源碼分析了 xxl-job 排程器的組成結構 以及 排程中心是如何觸發任務的。
排程器主要包含了以下子產品:
- schedulerThread: 負責從資料中心掃描需要執行的任務
- ringThread: 負責精準地控制預計需要執行的任務
- 快慢線程池:通過包裝兩個線程池,去分别執行 快任務 和 慢任務 的排程過程。
這裡面的主要難點是引出了一個時間輪的概念,文中并未做詳細的描述,另外時間輪的用法在很多地方都有應用, 比如 Netty、Dubbo、Kafka 等。
最後,本文中仍有很多細節沒有展開來講,需要讀者自行去挖掘。