天天看點

說完了 xxl-job 的執行器原理,再來聊聊排程中心是如何排程任務的

前言

在上一篇 xxl-job 執行器原理分析 一文中,我們提到了 xxl-job 架構中包含了兩個核心子產品:排程中心 和 執行器, 其中排程中心主要負責 任務的排程 , 而執行器負責 任務的執行, 兩者各司其職。 緊接着我們通過畫圖的方式對 執行器 的内部構造進行了分析,并且還對 Job 的執行流程進行了梳理。

本文我們繼續圍繞任務的排程流程對 排程中心 進行剖析, 内容依然參照 xx-job v2.x 版本的源碼。

正文

再看一遍 xxl-job 架構圖:

說完了 xxl-job 的執行器原理,再來聊聊排程中心是如何排程任務的

排程中心主要提供了兩個功能: 系統管理 和 任務排程。其餘的都是一些輔助功能。

  • 系統管理正如圖中所示的那樣, 包括任務管理、執行器管理、日志管理。還提供了管理界面。
  • 任務排程就是負責從資料中心拉取任務,并按照執行時間将任務投遞給執行器。

排程器的組成結構

說完了 xxl-job 的執行器原理,再來聊聊排程中心是如何排程任務的

兩個核心線程

當排程中心啟動後,會啟動以下兩個線程:

  1. schedulerThread

    scheudlerThread

    主要做如下兩件事情:
  • 從資料中心(db),也就是

    xxl_job_info

    表中掃描出符合 條件 1 的任務, 條件1 限制如下:
    • 任務執行時間 小于(目前時間 + 5 s)
    • 限制掃描個數, 這個值是動态的,會根據後面的提到的 快慢線程池 中線程數量有關系。
    count = treadpool-size * trigger-qps  (each trigger cost 50ms, qps = 1000/50 = 20) 
    
    treadpool-size = (getTriggerPoolFastMax() + getTriggerPoolSlowMax()) * 20
    // 看完快慢線程池的介紹,再回過頭來看這裡,會更容易了解
               
  • 掃描出來的任務被劃分為以下 3 類:
    說完了 xxl-job 的執行器原理,再來聊聊排程中心是如何排程任務的
  1. ringThread

ringThread

的作用就是不斷從 容器 中讀取 目前時間點需要執行 的任務, 讀取出來的任務會交給一個叫 快慢線程池 的東西去将任務傳遞給排程器去執行。

時間輪

上述的

ringThread

和 容器 共同組成了一個時間輪。

關于時間輪,如果展開來講内容會很多,需要詳細了解的可以參考 這篇文章,或者自行搜尋, 本文隻做簡單了解。

簡單來講,時間輪實作了 延遲執行 的功能,它在 xxl-job 中的作用就是讓 還未到達執行時間 的任務,按照預計的時間通過 快慢線程池 一個一個送到 執行器 中去執行。

  • 時間輪的資料結構一般是 數組 + 連結清單, 和 jdk1.7 中的 HashMap 是一個道理,連結清單中的每個節點就是一個待執行的任務。
  • xxl-job 中的時間輪可以形象描述為以下這張圖,像一個隻有秒針的鐘表一樣。
  • ringThread 線程運作過程中,每秒會掃過一個刻度,假設目前刻度位置存在 job 連結清單,就把連結清單中的所有 job 取出來,最後丢給 快慢線程池。
  • 當然 xxl-job 為了避免處理耗時太長,會跨過刻度,多向前校驗一個刻度;也就是當指針指到 2s 時,會把 1s 和 2s 位置的任務同時讀取出來。
    說完了 xxl-job 的執行器原理,再來聊聊排程中心是如何排程任務的

快慢線程池

上面提到任務從資料中心掃描出來後,随之就會被丢到快慢線程池中,快慢線程池的定義如下:

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 等。

最後,本文中仍有很多細節沒有展開來講,需要讀者自行去挖掘。

繼續閱讀