1. 程式人生 > >分散式任務排程平臺 XXL-JOB

分散式任務排程平臺 XXL-JOB

2017 年 10 月 22 日,又拍雲 Open Talk 聯合 Spring Cloud 中國社群成功舉辦了“進擊的微服務實戰派上海站”。大眾點評研發工程師許雪裡作了《XXL-JOB :分散式任務排程平臺》,以下是分享整理:

一、XXL-JOB 簡介

(一)分散式系統對任務排程的幾點要求

隨著公司推進微服務,我們一些 JOB 之前單體執行的時候,可能開發在某一個專案裡面,這個時候我們需要一個平臺去維護與開發。在這種場景下對一些任務排程的時候,會有下述我總結出來六點的要求。

1、平臺

平臺有一個好處,可以把我們 JOB 開發的基礎站統一起來,新手學習的時候,付出一些學習成本就可以快速開發;另外我們把所有的之前散落在各個專案中間的 JOB 彙總起來,至於一些相通的業務邏輯,尤其是我們任務出現依賴關係的時候,一些子任務依賴的任務邏輯,可以完全通過流程圖的方式進行復用;第三就是自維護和擴充套件,一旦把這個任務平臺抽象成服務時,我們在這個平臺進行升級一些功能擴充套件的時候,就不需要挨個去對接各個業務,直接在平臺上擴充套件就好。

2、HA/叢集

這其實是兩個方面的。任務排程可以拆成兩個詞,一個叫排程,一個叫任務。我們把這些平時寫的 JOB 中一些調不動的屬性抽離出來,其實這就是一個排程中心。但是寫 JOB 更重要的一塊是要實現一些業務邏輯,這些就是程式碼的業務,也就是任務。排程中心要保證它的高可用,這樣即使排程中心其中一臺宕機了,至少保持一臺正常地出發排程。針對我們的任務機器,假如說一個大資料量的任務,可能現在三臺機器把資料進行分辨處理,突然某一天資料量很大,業務發展很快的情況下,可以很快的對任務機器進行叢集,這樣面對這些激增的任務資料的時候,可以高效處理。

3、彈性擴容

這是針對我們任務的執行機器,也就是執行器。剛才也講到在我們業務發展的時候,這個季度我們可能業務量是百萬,下個月可能就是千萬了,甚至更大的資料,針對這個數量的時候,執行器可以支援快速的或者是線上動態的、彈性的擴容。

4、故障處理

故障處理有 failover、失敗告警。假如兩點鐘你收到這個失敗告警的時候,目前預設是通過郵件的策略,以及預留了一些告警的介面。當任務失敗了,推送一條訊息。假如你看到的情況下,可以快速登入到排程系統,手動觸發一次,下一次可能這個 JOB 的業務邏輯已經跑完了,等到八點的時候,老闆要這份報表的時候可以正常的交給他。如果你設定了failover,即我們的故障處理的策略,它在任務跑失敗的情況下會自動執行,自動重試。

5、阻塞處理

在頻率很高任務場景的時候,比如每天 PU 量的計算,可能一個任務要執行一個小時,這時候多次任務排程,可能會產生堆積。任務堆積的時候你需要提供一些策略,創新處理的話可以提供一個佇列。另外還有一種處理策略,把之前的 case 掉,重新跑一次 JOB。假如說你認可之前 JOB 跑的業務邏輯的話,也可以選擇另外一種,把之前的標記它成功,後面一次標記為失敗。

6、高效能

XXL-JOB 對排程的整個環節進行了全非同步化,一條任務進行觸發的時候,第一步它從觸發的排程中心到你執行器的時候,這時候會通過佇列的方式。排程中心把訊號觸發回去之後,立刻入佇列,結果立即響應,這是第一個觸發的過程。執行器接到訊號之後,會走第二步,會自己每個任務隔離的有一個佇列,然後挨個的處理你的訊息,處理完之後針對單條訊息有一個批量回調。回撥也有一個佇列。在觸發和執行這兩個地方,XXL-JOB 都進行了快取和佇列的處理。

7、自運維

在寫 JOB 的時候,你排查一個現場任務,比如你一個 JOB 怎麼樣確認它有沒有跑成功,以及跑的日誌是怎麼樣子的,如何把當前跑的日誌跟其他的日誌剝離開。尤其是你跑一個分辨任務的時候,可能插個十片的時候,你怎麼確定第五片的 JOB 是跑成功的,而且是跑的日誌的每一條資料都記錄出來。之前我的做法是上每一臺機器 S 登上去,把日誌拉下來。但是會發現,假如說一個執行器上面寫多個任務的話,它們的任務是耦合在一起的任務 log,這樣基本上想去隔離每個任務打的 log 幾乎是不可能的。

所以 XXL-JOB 做了另一件事情,第一個版本是基於 logFor 件自研了日誌的元件,後面有一些使用者他們對 logfor 件不太滿意,希望用 logback 這種,所以在這個基礎之上做了一個自研的日誌元件,通過日誌元件把每一個任務,每一次排程的日誌單獨寫小檔案。你只要登入到我們的XXL-JOB 排程中心的平臺上,就可以看到這個任務它的每一次排程,它觸發的狀態,觸發的時間狀態、引數,以及在執行器上執行的時間、狀態,以及中間的一些流程。甚至在你的執行過程中,你點進去的時候,可以在遠端的拉,每一個任務,每一次觸發小日誌的指令碼,小日誌觸發日誌檔案,以輪循的方式,瀑布流的方式,一行一行地加載出來

(二)XXL-JOB 簡介

XXL-JOB 是一個輕量級分散式任務排程框架,擁有上面提到的七個特點。它核心原理就是有一個模組叫做排程中心,另外一個模組叫做執行性器,它會把排程和任務執行,隔離成兩個部分。這樣你的排程模組只需要負責你任務的一些排程屬性,觸發你的排程訊號就行了。你執行模組只需要接入訊號,去執行具體的業務邏輯,兩者可以各自的進行擴容。

排程中心目前是隻有 Quartz,這個我們遇到一些限制,雖然享受著它的一些特性。

image.png

XXL-JOB 架構圖 v1.8

上圖是 XXL-JOB 1.8 的架構圖,首先會維護一些執行器管理的的一些註冊 APP name。一個 APP name 對應一個執行器的叢集,叢集下面機器列表。執行器下面繫結一些任務,每個任務執行的日誌資訊,還有其他的模組,運營報表,失敗告警,任務依賴等屬性。這個就是維護程式碼裡面的,整個組成一個排程中心,排程中心通過 Quartz 負責任務觸發,每次任務執行的時候,首先會根據這個任務找到對應的執行器,執行器在 APP name 找到註冊上來的一些機器的列表,根據一些路由的策略選中一臺機器,觸發右側執行器的任務執行。

執行器每個任務都存在一個執行緒,執行緒裡面有排程佇列,排程資訊入佇列之後,會一個一個地去處理排程佇列裡面的排程請求。然後這個任務在執行過程中的日誌會通過自研的日誌元件輸出一個一個的日誌小檔案裡。這樣你在排程中心介面,它只要執行了就會有一條排程日誌,通過排程日誌右側的日誌 log,就會看到當前這次排程它實時列印的一些日誌資訊。

當你的任務最終執行完成的時候,會把你當前的日誌執行結果推送回到執行緒的一個佇列,會週期性地把每個任務執行的結果推送到排程中心,在排程中心的 UI 介面上就可以看得到每一條任務的觸發情況、執行情況等。

從這個模組我們可以看到它有一個註冊服務,每個執行器會指定一個 APP name 。假如說我們拋開對比 SOE,就是服務化的話,一個 APP name 就相當於一個服務,以大的來說就是一個介面或者 serverID。不同的執行器就註冊在不同的 APP name 下面,也就是註冊在不同的執行器下面。但是每個執行器內嵌的包已經為它實現好了當前執行器統一介面下面的一些實現,這樣的話你註冊過來的執行器,我知道你的地址,也知道你依賴包所能提供一切的服務,這樣就可以用服務的方式請求我的執行器。這種通訊就是 IPC 的方式,是非常方便擴充套件的。

二、XXL-JOB 特性

以下是 XXL-JOB 的一些特性全覽,每一個特性我都是當時寫的時候,這是一個演進的過程,我會主要會講一下執行器和排程中心的物理結構圖,以及 HA/叢集。

  • 1、簡單:支援通過Web頁面對任務進行CRUD操作,操作簡單,一分鐘上手;
  • 2、動態:支援動態修改任務狀態、暫停/恢復任務,以及終止執行中任務,即時生效;
  • 3、排程中心HA(中心式):排程採用中心式設計,“排程中心”基於叢集Quartz實現並支援叢集部署,可保證排程中心HA;
  • 4、執行器HA(分散式):任務分散式執行,任務"執行器"支援叢集部署,可保證任務執行HA;
  • 5、註冊中心: 執行器會週期性自動註冊任務, 排程中心將會自動發現註冊的任務並觸發執行。同時,也支援手動錄入執行器地址;
  • 6、彈性擴容縮容:一旦有新執行器機器上線或者下線,下次排程時將會重新分配任務;
  • 7、路由策略:執行器叢集部署時提供豐富的路由策略,包括:第一個、最後一個、輪詢、隨機、一致性HASH、最不經常使用、最近最久未使用、故障轉移、忙碌轉移等;
  • 8、故障轉移:任務路由策略選擇"故障轉移"情況下,如果執行器叢集中某一臺機器故障,將會自動Failover切換到一臺正常的執行器傳送排程請求。
  • 9、失敗處理策略;排程失敗時的處理策略,策略包括:失敗告警(預設)、失敗重試;
  • 10、失敗重試:排程中心排程失敗且啟用"失敗重試"策略時,將會自動重試一次;執行器執行失敗且回撥失敗重試狀態時,也將會自動重試一次;
  • 11、阻塞處理策略:排程過於密集執行器來不及處理時的處理策略,策略包括:單機序列(預設)、丟棄後續排程、覆蓋之前排程;
  • 12、分片廣播任務:執行器叢集部署時,任務路由策略選擇"分片廣播"情況下,一次任務排程將會廣播觸發叢集中所有執行器執行一次任務,可根據分片引數開發分片任務;
  • 13、動態分片:分片廣播任務以執行器為維度進行分片,支援動態擴容執行器叢集從而動態增加分片數量,協同進行業務處理;在進行大資料量業務操作時可顯著提升任務處理能力和速度。
  • 14、事件觸發:除了"Cron方式"和"任務依賴方式"觸發任務執行之外,支援基於事件的觸發任務方式。排程中心提供觸發任務單次執行的API服務,可根據業務事件靈活觸發。
  • 15、任務進度監控:支援實時監控任務進度;
  • 16、Rolling實時日誌:支援線上檢視排程結果,並且支援以Rolling方式實時檢視執行器輸出的完整的執行日誌;
  • 17、GLUE:提供Web IDE,支援線上開發任務邏輯程式碼,動態釋出,實時編譯生效,省略部署上線的過程。支援30個版本的歷史版本回溯。
  • 18、指令碼任務:支援以GLUE模式開發和執行指令碼任務,包括Shell、Python、NodeJS等型別指令碼;
  • 19、任務依賴:支援配置子任務依賴,當父任務執行結束且執行成功後將會主動觸發一次子任務的執行, 多個子任務用逗號分隔;
  • 20、一致性:“排程中心”通過DB鎖保證叢集分散式排程的一致性, 一次任務排程只會觸發一次執行;
  • 21、自定義任務引數:支援線上配置排程任務入參,即時生效;
  • 22、排程執行緒池:排程系統多執行緒觸發排程執行,確保排程精確執行,不被堵塞;
  • 23、資料加密:排程中心和執行器之間的通訊進行資料加密,提升排程資訊保安性;
  • 24、郵件報警:任務失敗時支援郵件報警,支援配置多郵件地址群發報警郵件;
  • 25、推送maven中央倉庫: 將會把最新穩定版推送到maven中央倉庫, 方便使用者接入和使用;
  • 26、執行報表:支援實時檢視執行資料,如任務數量、排程次數、執行器數量等;以及排程報表,如排程日期分佈圖,排程成功分佈圖等;
  • 27、全非同步:系統底層實現全部非同步化,針對密集排程進行流量削峰,理論上支援任意時長任務的執行。

(一)HA/叢集

image.png

當我們部署一套 XXL-JOB 的時候,首先我們的排程中心最好部署兩臺,排程中心會指向同一個 MySQL  例項,這樣排程中心一定程度上可以保證它的 HA 。它每次排程的時候會通過一個遠端任務代理的請求,觸發到遠端的執行器。在部署遠端執行器的時候,只要把每一個機器指向同一個 APP name,這樣每個執行器會以心跳註冊的方式向排程中心進行註冊,它也是 30 秒註冊一次,三次心跳。如果是連續三次心跳中斷的話,會把當前的執行器摘除掉。這樣排程中心發現三次心跳之內都在存活這個執行器的情況下,會把它視為一個存活的執行器。在下次任務路由任務觸發的時候,會當做一個備選項。排程中心和執行器之間的通訊也是全非同步化的,從任務的觸發,到執行,到結果回撥,如果可以入佇列的話,都通過佇列的方式進行。

(二)彈性擴容

image.png

彈性擴容

執行器的彈性擴充,主要是得益於它的註冊中心。註冊中心其實本質上是一個 API 服務,有三個主要的 API,第一個是心跳註冊,假如當前是1、2 兩臺叢集,這時候上線一臺 3,3 會在啟動的時候立刻向排程中心進行自我的初測。在排程中心找到,比如這個業務線是餐飲,餐飲下面本來有兩個執行器,在 30 秒之後排程中心重新整理它註冊機器列表的時候,就會把餐飲 的執行器給刷出來。然後在下次心跳之後的排程請求,就會將 3 納入它的路由機器的備選項。

第二個是摘除,摘除還以 3 為例,在機器宕機或者銷燬的時候,它會主動把自己摘除掉,讓排程中心說我過期了,然後把自己摘除掉;排程中心如果發現這個執行器在三次心跳之內都沒有有效的註冊,會把它主動過期摘除掉。這就是兩種進行機器摘除的方式。

第三是註冊機器查詢,主要是任務排程的時候篩選機器。我們是一個任務寫在一個執行器下,這個執行器可能是叢集的。假如有三臺機器或者多臺,如何去篩選,具體執行本次任務的邏輯是什麼?這裡提供一些策略。

(三)執行器路由策略

image.png

執行器路由策略

前兩個主要是線上,只有兩臺機器,第一個和最後一個,不是一就是二的情況。後面主要是輪詢和隨機。如果你對它要求不太高,還有一個一致性策略,你有三臺執行器,但是 JOB 可能 1、2、3 層,一個執行器下面可能寫了很多個 JOB ,但是執行器的任務數量是一致的,如何把當前的任務每一次排程永遠雜湊到固定一個執行器下面,一致性這個策略就可以保證這種功能。然後就是AIFU和AIRU這兩種。

(四)故障轉移 & 忙碌轉移

image.png

故障轉移 & 忙碌轉移

假如執行器叢集部署,有三臺機器,首先會對機器列表進行自然排序,然後從第 1 臺開始,第2 臺,直到一次觸發成功的時候,會進行觸發,之前的會標記失敗。在排程軌跡可以看到,第 1 臺觸發失敗,第 2 臺觸發失敗,第 3 臺觸發成功,最終實現任務觸發的成功。

忙碌轉移,可能一個 JOB 需要在 3 臺執行器上執行,但是不希望已經執行的這臺機器還在執行,需要找一臺空閒機器執行。這樣首先會觸發,同樣三臺機器,會把觸發任務執行的訊號發過去。機器 1,在忙碌,返回一個狀況或者原因標記。這時候它會向機器 2 傳送請求,2 返回在忙,然後轉向 3 ,3 返回空閒,然後接著這個請求訊號進行觸發。

(五)分片任務 & 動態分片

image.png

分片任務 & 動態分片

目前是通過分片廣播的方式實現分片。我們匹配到執行器以後,可以拿到註冊中心所有機器的列表,然後進行自然排序,每一個機器本身就有一個序號,它的總數量也是有序的,可以這兩個引數傳遞到每一個機器。第一個機器傳了一個 0-3 ,它就是識別到我是第 1 臺機器,而且總共的機器數量是 3 ,根據這兩個引數做一次出訪,對你的總數字量進行分片。 純廣播類的 JOB 也可以用這種方式,比如動態重新整理一些資料的。

接下來介紹動態分片。假如我們現在總資料量有 3 萬條資料,任務進行觸發了,但是線上的執行器可能是 3 臺。當觸發的時候會分別向 0、1、2 進行傳送請求。這時候 0 接受到的引數就是03,2 就是 13,最後一個是 23。這樣根據引數對總資料進行分片,把最大資料拿到,進行處理。這樣假如當前有一臺新機器上線了,或者機器 4 上線了,在下一次觸發的時候,會以新的分片引數作為請求引數進行觸發。

(六)阻塞策略 & 失敗處理策略

image.png

當我們有一些耗時任務,觸發的頻率超過它的執行器所執行的那些速度的時候,如上圖,紅色的觸發請求進來,但是前面的還在堆積著執行,這時候怎麼辦?第一條就是預設的單機序列,會把請求入佇列,等前面的執行完了之後,挨個把所有的觸發的任務全都執行掉。第二個就是丟棄後續的排程,紅色的進來了,發現前面已經有了,或者是當前已經 JOB 運行了,直接把後面的標記失敗,不進行後面的執行了。最後一個就是覆蓋之前排程的,它發現前面佇列裡面的資料或者任務執行的情況下,把佇列清空,把清空的資料全都標記失敗,然後把執行的 JOB 也標記失敗,讓自己來執行。

失敗處理有一個失敗告警,你可以預設是郵件告警,只要每個任務配一下,可以備多個。當每一次任務觸發,如果是觸發失敗,執行失敗的情況下,會向你的接收人發一封郵件。現在也已經預留了介面,假如你需要簡訊的方式告警,或者是微信的方式告警,可以自己二次開發擴充套件。也提了一些失敗重試的策略,當你的任務執行失敗的時候,選擇這個策略,會在當前線上的機器列表裡面去尋找下一個機器進行重試。

(七)觸發規則

現在提供的觸發規則主要有 3 種。第一種是 Cron 表示式,每一個任務需要配一個 Cron 表示式。第二種是任務依賴,你可以為每一個任務配置一個子任務,當副任務執行完成之後,可以觸發子任務,這樣關聯的方式進行觸發執行。第三種就是事件觸發,其實就是類似於 Mq 的場景,程式碼裡面有一個業務邏輯,觸發了一個任務執行。

(八)任務模式

接下來介紹 XXL-JOB 的任務模式。第一個就是 Bean 模式,本質上是 Spring 的一個 Bean,加一個註解之後,會自動被執行器掃描掉,執行器本身上報到執行中心,然後就被找到。

第二個就是 GLUE 模式的 JAVA。GLUE 模式是這樣一個模式,假設一個系統是一輛車,上線一個新功能的時候可能擰一個螺絲,每次上螺絲的時候,先把車停下來,把螺絲擰上去;但是 GLUE 好比膠水,上線一個新功能的時候,車還在跑,直接膠水貼上,不管車是停止還是開動。可能會有失敗,但是 log 會追蹤。你的業務還在跑的情況下,去增加一些業務邏輯,或者是增加一些新的 JOB。

GLUE 模式目前支援 java、shell、python、node JS 這幾種模式,它們的開發或者上線,全部都在 web UI 上提供了 wed IDE 進行開發的,無需打包上線。因為原始碼寫在了排程中心,所以它也提供了一些基本的版本回溯。

GLUE java 的這種模式,是通過 GLUE Class loader 載入原始碼的方式,載入原始碼可以注入 spring 當中其他的一些 server 元件,很方便的接觸 spring 其他服務。你修改的時候,下次任務執行,會是你當前這份原始碼重新例項化,注入一些新的服務進行執行。

(九)執行日誌 & Rolling Log

image.png

任務日誌和 Rolling Log ,上圖可以看到是一條一條的日誌,其實是對應到任務觸發的每一條,它的執行的歷史。第一個紅框主要是它觸發的時間、狀態,以及觸發的引數。黑框如果可以看得到,當前觸發選擇一些當前的機器列表和路由策略。右側的是任務執行一側的日誌資訊,在執行器上執行的時間,以及它執行的狀態,以及最終執行時候中間的執行 log。

(十)構建方式

構建方式比較簡單,所有的依賴只有一個 JDK 和 Mysql ,在 Mysql 裡面初始化你的指令碼,第二步編輯你的排程中心,第三步是編譯部署執行器。提供了幾種 Sample 的專案,有Spring Boot,還有 Spring 外包方式的,上面三步部署完之後,就是可以開發的一個JobHandler。