一、分散式任務排程的背景

無論是網際網路應用或者企業級應用,都充斥著大量的批處理任務。我們常常需要一些任務排程系統來幫助解決問題。隨著微服務化架構的逐步演進,單體架構逐漸演變為分散式、微服務架構。在此背景下,很多原先的任務排程平臺已經不能滿足業務系統的需求,於是出現了一些基於分散式的任務排程平臺。

1.1 分散式任務排程的演進

在實際業務開發過程中,很多時候我們無可避免地需要使用一些定時任務來解決問題。通常我們會有多種解決方案:使用 Crontab 或 SpringCron (當然這種情況可能機器很少而且任務簡單又不是很多的情況下)。然而,當應用複雜度升高、定時任務數量增多且任務之間產生依賴關係時,Crontab 進行定時任務的管理配置就會非常混亂,嚴重影響工作效率。這時就會產生一系列問題:

  • 任務管理混亂,生命週期無法統一協調管理;
  • 任務之間如果存在依賴關係,難以編排。

隨著網際網路的發展,分散式服務架構勢越來越流行。相應的也需要一個分散式任務排程系統來管理分散式架構中的定時任務。

1.2 分散式任務排程架構

當垂直應用越來越多,應用之間互動也會越來越複雜,通常我們採用分散式或者微服務架構,將核心業務抽取出來,形成單獨的服務。一個獨立的微服務群體逐漸形成穩定的服務中心,使得業務應用能更快地響應多變的市場需求。

此時,用於提高業務複用及整合的分散式服務框架成為關鍵。同時,由於服務獨立,一般能做到定時任務獨立的情況,任務的更改對於整體系統的影響小之又小。通常我們會採用任務與排程分離的方式(如上圖所示),任務的執行邏輯無需關注排程與編排,同時可以保證執行器和排程的高可用,易於開發和維護。

1.3 分散式任務排程優勢

在分散式服務架構的基礎上,由於獨立業務的數量可能很多,此時如果定時任務單獨在該服務中實現,很可能會出現難以管理的情況,且避免不了由於定時任務的更改而導致的業務重啟。因此,一個獨立的分散式任務排程系統是很必要的,可以用來全域性統籌管理所有的定時任務。同時,將任務的配置單獨抽離出來,作為該分散式任務排程系統的功能,就能做到定時任務的更改不影響任何業務,也不影響整個系統:

  • 通過排程與任務分離的方式進行管理,大大降低了開發和維護成本;
  • 分散式部署,保證了系統的高可用性、伸縮性、負載均衡,提高了容錯性;
  • 可以通過控制檯部署和管理定時任務,方便靈活高效;
  • 任務都可以持久化到資料庫,避免了宕機和資料丟失帶來的隱患,同時有完善的任務失敗重做機制和詳細的任務跟蹤及告警策略。

二、分散式任務排程技術選型

2.1 分散式任務排程考慮因素

  • 任務編排:多個業務之間的定時任務存在流程次序。
  • 任務分片:對於一個大型任務,需要分片並行執行。
  • 跨平臺:除了使用 Java 技術棧(SpringBoot、Spring等)的專案之外,還有使用其他語言的應用。
  • 無侵入:業務不希望與排程高耦合,只關注業務的執行邏輯。
  • 故障轉移:任務執行過程中遇到問題有補償措施,減少人工介入。
  • 高可用:排程系統自身必須保證高可用。
  • 實時監控:實時獲取任務的執行狀態。
  • 視覺化:任務排程的操作提供視覺化頁面,方便使用。
  • 動態編輯:業務的任務時鐘引數可能變動,不希望停機部署。

2.2 SIA-TASK與其它分散式任務排程技術比較

SIA是宜信公司基礎開發平臺Simple is Awesome的簡稱,SIA-TASK(微服務任務排程平臺)是其中的一項重要產品,SIA-TASK契合當前微服務架構模式,具有跨平臺、可編排、高可用、無侵入、一致性、非同步並行、動態擴充套件、實時監控等特點。

開源地址:https://github.com/siaorg/sia-task

我們先對比市場上主流的開源分散式任務排程框架,分析其優缺點,然後再介紹我們的技術選型。

  • Quartz: Quartz 是 OpenSymphony 開源組織在任務排程領域的一個開源專案,完全基於 Java 實現。該專案於 2009 年被 Terracotta 收購,目前是 Terracotta 旗下的一個專案。相比於 JDK 或 Spring 提供的定時任務,Quartz 對單個任務的控制基本做到了極致,以其強大功能和應用靈活性,在企業應用中發揮了巨大的作用。然而 Quartz 並不支援任務的編排(任務之間有依賴),而且不支援任務分片。
  • TBSchedule: TBSchedule 是一個支援分散式的排程框架,能讓一種批量任務或者不斷變化的任務,被動態地分配到多個主機的 JVM 中,不同的執行緒組中並行執行。基於 ZooKeeper 的純 Java 實現,由 Alibaba 開源。TBSchedule 側重於任務的分發,支援任務分片,但是沒有任務編排,也不是跨平臺的。
  • Elastic-Job: Elastic-Job 是噹噹開源的一個分散式排程解決方案,由兩個相互獨立的子專案Elastic-Job-Lite 和 Elastic-Job-Cloud 組成。Elastic-Job 支援任務分片(作業分片一致性),但是沒有任務編排,也不是跨平臺的。
  • Saturn: Saturn 是唯品會開源的分散式,高可用的排程服務。Saturn 在 Elastic-Job 做二次開發,支援監控、任務分片、跨平臺,但是沒有任務編排。
  • Antares: Antares 是基於 Quartz 的分散式排程,支援分片、支援樹形任務依賴,但不是跨平臺的。
  • Uncode-Schedule: Uncode-Schedule 是基於 Zookeeper 的分散式任務排程元件。支援所有任務在叢集中不重複、不遺漏的執行。支援動態新增和刪除任務。但是不支援任務分片,也沒有任務編排,還不是跨平臺的。
  • XXL-JOB: XXL-JOB 是一個輕量級分散式任務排程平臺,其核心設計目標是開發迅速、學習簡單、輕量級、易擴充套件。XXL-JOB 支援分片,簡單支援任務依賴,支援子任務依賴,不是跨平臺的。

下面我們簡單對比下 SIA-TASK 與這些任務排程框架:

 任務編排任務分片跨平臺高可用故障轉移實時監控
SIA-TASK
Quartz × × .NET × API監控
TBSchedule × ×
Elastic-Job × ×
Saturn ×
Antares ×
Uncode-Schedule × × ×
XXL-JOB 子任務依賴 ×

可以發現,這些排程框架基本上都支援高可用、故障轉移與實時監控等功能,但是對於任務編排、任務分片與跨平臺等功能的支援各有側重點。SIA-TASK 將全面支援這些功能。

三、SIA-TASK介紹

3.1 SIA-TASK技術選型

  • REST:一種軟體架構風格。要求執行器暴露 Http 呼叫介面來達到跨平臺的目的。
  • AOP:切面程式設計技術。在 Spring 專案擴充套件包 Hunter 中使用,保證 Task 被序列呼叫(單例單執行緒)。
  • Quartz:功能強大,應用靈活,對單個任務的控制基本做到了極致,用來作為排程中心時鐘元件。
  • MySQL:用於元資料儲存與(暫時)日誌存取。
  • Elastic:基於 Lucene 的搜尋伺服器,提供了一個分散式多使用者能力的全文搜尋引擎,用於日誌的儲存與查詢。
  • SpringCloud:社群活躍的開發框架,也是公司指定的統一開發框架。用於快速開發,快速迭代。
  • MyBatis:一款優秀的持久層框架,支援定製化 SQL,儲存過程以及高階對映。用於簡化持久層開發。
  • Zookeeper:久經考驗的註冊中心。用來解決排程中心高可用,分散式一致性等問題。

3.2 SIA-TASK設計思想

SIA-TASK借鑑微服務設計思想,獲取分佈在每個執行器節點上的任務(Task)元資料,進行彙報,上傳註冊中心。利用線上可編輯方式支援任務線上編排、動態修改任務時鐘;使用 Http 協議作為互動傳輸協議。資料互動格式統一使用Json。使用者通過編排器(下文會做介紹)進行操作,觸發事件,排程器接收事件,由排程中心進行時鐘解析,執行任務流程,進行任務通知。

3.3 SIA-TASK基本概念

SIA-TASK 採用任務和排程分離的方式,業務的執行任務邏輯和排程邏輯完全分離。系統組成共涉及以下幾個核心概念:

  • 任務(Task): 基本執行單元,執行器對外暴露的一個HTTP呼叫介面。
  • 作業(Job): 由一個或者多個存在相互邏輯關係(序列/並行)的任務組成,任務排程中心排程的最小單位。
  • 計劃(Plan): 由若干個順序執行的作業組成,每個作業都有自己的執行週期,計劃沒有執行週期。
  • 任務排程中心(Scheduler): 根據每個的作業的執行週期進行排程,即按照計劃、作業、任務的邏輯進行HTTP請求。
  • 任務編排中心(Config): 編排中心使用任務來建立計劃和作業。
  • 任務執行器(Executer): 接收HTTP請求進行業務邏輯的執行。
  • Hunter:Spring專案擴充套件包,負責執行器中的任務抓取,上傳註冊中心,業務可依賴該元件進行Task編寫。

3.4 SIA-TASK系統架構

SIA-TASK 可以分為三大模組(排程中心、編排中心和執行器)、兩大元件(持久化儲存和註冊中心)。這三大模組和兩大元件的作用如下:

  • 任務排程中心:負責搶佔Job和任務排程以及任務遷移等,是SIA-TASK 的核心功能模組。
  • 任務編排中心:負責對線上任務進行邏輯編排,提供日誌檢視和實時監控功能。
  • 任務執行器:負責接收排程請求並執行任務邏輯。
  • 任務註冊中心(ZK):協調Job和Task、排程器等的工作流程。
  • 持久化儲存(DB):記錄專案的Job和Task資料,並提供日誌儲存。

SIA-TASK 使用 SpringBoot 體系作為架構選型,基於Quartz及Zookeeper進行二次開發,支援相應的特性功能,SIA-TASK 的邏輯架構圖如下圖所示:

3.5 SIA-TASK模組說明

3.5.1 任務排程中心

任務排程中心負責任務排程,管理排程資訊,按照排程配置發出排程請求,自身不承擔業務程式碼。排程系統與任務解耦,提高了系統可用性和穩定性,同時排程系統性能不再受限於任務模組;支援視覺化、簡單且動態地管理排程資訊,包括任務新建,更新,刪除和任務報警等,所有上述操作都會實時生效,同時支援監控排程結果以及執行日誌,支援執行器故障恢復。

3.5.2 任務編排中心

任務編排中心是分散式排程中心支援線上任務模型編排的元件;依託於UI可進行web端任務編排。

我們可以通過上述基礎模型來編排一些複雜的排程模型,例如:

SIA-TASK的UI編排介面:

編排結束後檢視task的編排資訊如下圖所示:

同時,編排中心還提供首頁統計資料檢視、排程監控、Job管理、Task管理以及日誌管理功能。

3.5.3 任務執行器

負責接收排程請求並執行任務邏輯。任務模組專注於任務的執行等操作,開發和維護更加簡單和高效;

執行器支援兩種型別:

(1) 如果使用 sia-task-hunter,支援SpringBoot專案和Spring專案, 引入 sia-task-hunter,任務(Task)抓取客戶端。合規的HTTP介面(稱之為Task)任務會自動被抓取並上傳註冊中心;

(2) 如果不使用 sia-task-hunter,只需提供任務可呼叫的HTTP介面,此時需要業務手動錄入,且自行控制該任務的併發呼叫控制。

3.5.4 任務註冊中心(Zookeeper)

分散式框架採用Zookeeper作為註冊中心。

(1) 任務註冊

排程中心和執行叢集都以Zookeeper作為註冊中心,所有資料以節點及節點內容的形式註冊,通過定時彙報主機狀態保持存活在Zookeeper上。

(2) 元資料儲存

註冊中心不僅僅提供註冊服務,並且儲存每個執行器的資訊(包括執行器例項資訊,執行器上傳的Task元資料,以及任務執行時的一些臨時狀態資料)。

(3) 事件釋出

基於Zookeeper事件推送機制,進行任務的釋出,通過平衡演算法保證排程器任務搶佔的分佈均衡。

(4) 負載均衡

保證排程器獲取執行Job的個數均衡,避免單一節點壓力。

3.5.5 持久化儲存(DB)

這裡採用MySQL作為資料持久化解決方案。

除了Task動態元資料儲存在註冊中心之外,其他相關的元資料都存入MySQL,包括但不限於:手動錄入的Task、配置的Job資訊、編排的Task依賴資訊、排程日誌、業務人員操作日誌、Task執行日誌等。

3.6 SIA-TASK關鍵執行流程

3.6.1 任務釋出流程

(1) 使用者可以通過UI進行Job建立。可以選擇Job型別,設定預警郵箱,設定Job描述。然後為建立的Job進行任務Task編排。

(2) Job建立完畢並且設定Task編排關係後可進行任務釋出,通過UI對相應的Job進行操作(啟用,執行一次,停止以及刪除操作)。

(3) 使用者的Task任務可以是通過抓取器抓取的,亦可以使用UI手動建立。

3.6.2 執行流程

(1) Job建立完成之後,可以選擇啟用觸發定時任務;

(2) Job到達預訂時間後,排程中心觸發Job,然後按照預定的Task編排邏輯通過http通知Task執行器進行執行,並非同步監聽任務執行結果;

(3) 若執行結果成功,則判斷是否存在後置Task,若存在,則繼續下一次排程,若不存在,則說明該Job執行完畢,結束本次呼叫;若執行結果失敗,則觸發故障恢復策略:立即停止、忽略本次失敗、多次嘗試、轉到其它執行器執行。

3.6.3 狀態流轉

Job在整個生命週期記憶體在四種狀態,分別是:已停止(NULL)、準備中(READY)、開始執行(RUNNING)、異常停止(STOP),狀態流轉及流轉條件如下圖所示。

3.7 SIA-TASK模組設計

SIA-TASK 的物理網路拓撲圖如下所示:

SIA-TASK 的模組間互動設計思路:

(1) 通過編排中心建立Task任務或通過Hunter自動抓取,並將 Task 資訊非同步儲存到DB;建立Job並激活,在zookeeper中建立JobKey。

(2) 排程中心會監聽zookeeper中JobKey建立事件,然後搶佔建立的Job,搶佔成功後加入quartz定時任務,當時間到達即觸發Job執行。排程中心非同步呼叫執行器服務執行Job中的 Task (可能存在多個 Task ,遵循 Task 失敗策略),並將結果返回到排程中心。

(3) 將Job執行狀態隨時在zookeeper上更改,通過編排中心的查詢介面可以進行查詢。

(4) Job執行結束後,等待下一次執行。

3.7.1 任務編排中心設計

編排中心可以與DB和zookeeper進行資料互動,其主要功能可分為三方面:

  • 資料持久化介面服務;
  • zookeeper上元資料變更;
  • 資料視覺化:檢視系統各種統計資料等。

編排中心首頁監控展示如下:

3.7.2 任務排程中心設計

排程中心主要與DB、ZK和執行器進行互動,其主要功能可分為以下幾個方面:

  • Job執行日誌記錄
  • ZK中Job狀態變更
  • 呼叫執行器服務執行Job
  • 排程中心高可用
  • Job 排程執行緒池

3.7.3 任務執行器設計

執行器可以與ZK和排程中心進行互動,其主要功能可分為兩個方面:

  • 接受排程中心的排程,執行定時任務,並將結果返回到排程中心;
  • 自動抓取執行器上的 Task 任務,提交到ZK。

執行器 Task示例:

@OnlineTask(description = "線上任務示例",enableSerial=true)
@RequestMapping(value = "/example", method = { RequestMethod.POST }, produces = "application/json;charset=UTF-8")
@CrossOrigin(methods = { RequestMethod.POST }, origins = "*")
@ResponseBody
public String example(@RequestBody String json) {   
    /**
     * TODO:客戶端業務邏輯處理
     */
    Map<String, String> info = new HashMap<String, String>();
    info.put("status", "success");
    info.put("result", "as you need");
    return JSONHelper.toString(info);
}

 

由此可見,任務 Task 編寫非常簡單。

3.8 SIA-TASK高可用設計

分散式服務一般都要考慮高可用方案,同樣 SIA-TASK 為了保證高可用,針對不同的服務元件進行了不同維度增強。

3.8.1 任務編排中心的高可用

SIA-TASK 通過前後端分離、服務拆分等措施實現了編排中心的高可用。當叢集中某例項失效後,不會影響叢集的其它例項,因此無需特殊操作即可使用叢集中其它的可用編排中心。

3.8.2 任務排程中心的高可用

3.8.2.1 異常轉移

如果排程中心叢集中的某個例項節點服務宕機後,這個例項節點上的所有Job會平滑遷移到叢集中可用的例項上,不會造成定時任務的執行缺失,同時,當崩潰後的例項修復成功重新接入該叢集時,會繼續搶佔Job提供服務。

3.8.2.2 配置執行緒池

排程採用執行緒池方式實現,避免單執行緒因阻塞而引起任務排程延遲。程池裡的執行緒數,預設值是10,當執行任務會併發執行多個耗時任務時,要根據業務特點選擇執行緒池的大小。

org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool org.quartz.threadPool.threadCount = 60
org.quartz.threadPool.threadPriority = 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true

 

SIA-TASK 根據quartz自身提供的threadPool再次進行執行緒池的利用。進行執行緒池重新定義,針對每個Job去分配一個獨有的執行緒池。執行緒池的大小可根據Job自身編排的 Task 個數的大小進行動態伸縮,從而保證每個Job的排程執行緒完全獨立,不在會因為編排 Task 個數的陡增而耗盡執行緒資源。同時提供執行緒池資源的回收邏輯,在Job進行永久性終止時回收為期分配的執行緒池資源。

public static ExecutorService getExecutorService(String JobKey) {

    ExecutorService exec = executorPool.get(JobKey);
     if (exec == null) {
        LOGGER.info(Constants.LOG_PREFIX + "Initialize thread pool for running Jobs,Job is {}",JobKey);
      exec = Executors.newCachedThreadPool();
      executorPool.putIfAbsent(JobKey, exec);
      exec = executorPool.get(JobKey);
  }
    return exec;
}

 

3.8.2.3 全日誌跟蹤

SIA-TASK 針對Job的整個排程生命週期進行全面跟蹤,利用AOP進行日誌增強,排程中心每觸發一次Job排程就會進行日誌記錄。同時針對Job編排的 Task 執行也會進行記錄任務日誌。

日誌分為Job日誌和 Task 日誌:

  • Job日誌:包含排程器資訊、排程時間、排程狀態以及其他附加屬性。
  • Task日誌:包含執行器資訊、執行時間、執行狀態、返回資訊以及其他附加屬性。
3.8.2.4 非同步封裝
  • SIA-TASK 從一開始設計就考慮了任務進行遠端呼叫對排程中心併發執行緒資源的損耗。對於Job封裝的 Task 遠端排程,全部採用非同步呼叫,每次任務請求邏輯的耗時非常的輕量化。只僅僅一次見到的http請求。
  • 支援 Task 進行使用者自定義超時設定,支援兩種模式的超時:connecttimeout、readtimeout。支援使用者根據業務的具體執行週期來進行超時設定。
public interface RestTemplate {

/**
 * 非同步Post方法 * @param request
 * @param responseType
 * @param uriVariables
 * @param <T>
 * @return
 */
 <T> ListenableFuture<ResponseEntity<T>> postAsyncForEntity(Request request, Class<T> responseType, Object... uriVariables); }

 

3.8.2.5 自定義排程器資源池

SIA-TASK 從物理資源角度設計了排程資源池,出於一些特殊情況的考量我們針對排程器進行了池化;排程器可以通過不同的操作進行狀態的轉變,從而進行能力的轉化。

  • 工作排程器資源池:管理具備獲取任務能力並且可以實際獲取任務的排程器資源。
  • 下線排程器資源池:管理具備獲取任務能力但是實際不允許獲取的排程器資源。
  • 離線排程器資源池:管理下線排程器資源池中已經宕機的排程器資源。

3.8.3 任務執行器的高可用

  • 考慮網路的不穩定性,SIA-TASK 針對網路的不穩定性也做出了非常重要的設計,對於節點的連通性的測試支援以及針對 Task 執行例項節點健康的預感知,保證提前感知 Task 例項節點的健康情況,保證排程 Task 高可用。

  • 同時也保證了執行器例項針對網路導致連結中斷的問題,SIA-TASK 重新設計了zookeeper的重連機制,保證 Task 執行例項節點因網路問題丟失連結後還能進行恢復重試,直到恢復正常後併入執行池中正常接收任務的排程。

  • 一般來說,執行器也是叢集部署的。作為 Task 的執行單元,如果在執行器叢集中一臺機器上執行失敗,排程中心會根據失敗策略來做故障轉移。這裡提供了兩種故障轉移策略:輪詢轉移和最大補償轉移。輪詢轉移為對可用的執行器列表進行輪詢,若有一個執行器執行成功,則 Task 執行成功,若全部執行失敗,則 Task 執行失敗。最大補償轉移為首先在本執行器再次執行若干次,若執行成功,則不會轉移,若還是執行失敗,則執行輪詢轉移策略。

四、總結

至此對微服務任務排程平臺 SIA-TASK 做了一個簡要的介紹,包括設計背景、架構設計以及產品元件功能與特性。微服務任務排程平臺 SIA-TASK 基本上解決了當前的業務需求,提供簡單高效的編排排程服務。SIA-TASK 會持續迭代,提供更為完善的服務。之後也會提供相關技術文件和使用文件。

連結指南

開源地址:https://github.com/siaorg/sia-task

拓展閱讀:宜信開源微服務任務排程平臺(SIA-TASK)

作者:毛正衛/李鵬飛/樑鑫

原文首發:SpringCloud