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

前言

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

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

正文

再看一遍 xxl-job 架構圖:

在這裏插入圖片描述

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

  • 系統管理正如圖中所示的那樣, 包括任務管理、執行器管理、日誌管理。還提供了管理界面。
  • 任務調度就是負責從數據中心拉取任務,並按照執行時間將任務投遞給執行器。

一、調度器的組成結構

在這裏插入圖片描述

1.1 兩個核心線程

當調度中心啓動後,會啓動以下兩個線程:

  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 類: 在這裏插入圖片描述
  1. ringThread

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

1.2 時間輪

上述的 ringThread和 容器 共同組成了一個時間輪。

關於時間輪,如果展開來講內容會很多,需要詳細瞭解的可以參考 這篇文章,或者自行搜索, 本文只做簡單瞭解。

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

  • 時間輪的數據結構一般是 數組 + 鏈表, 和 jdk1.7 中的 HashMap 是一個道理,鏈表中的每個節點就是一個待執行的任務。

  • xxl-job 中的時間輪可以形象描述爲以下這張圖,像一個只有秒針的鐘表一樣。

  • ringThread 線程運行過程中,每秒會掃過一個刻度,假設當前刻度位置存在 job 鏈表,就把鏈表中的所有 job 取出來,最後丟給 快慢線程池

  • 當然 xxl-job 爲了避免處理耗時太長,會跨過刻度,多向前校驗一個刻度;也就是當指針指到 2s 時,會把 1s 和 2s 位置的任務同時讀取出來。 在這裏插入圖片描述

1.3 快慢線程池

上面提到任務從數據中心掃描出來後,隨之就會被丟到快慢線程池中,快慢線程池的定義如下:

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

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章