ElasticJob淺談
1 什麼是Elastic-Job
Elastic-Job是噹噹網開源的一個分散式排程解決方案,基於Quartz二次開發的,由兩個相互獨立的子專案ElasticJob-Lite和Elastic-Job-Cloud組成。Elastic-Job-Lite,它定位為輕量級無中心化解決方案,使用Jar包的形式提供分散式任務的協調服務,而Elastic-Job-Cloud子專案需要結合 Mesos 以及Docker在雲環境下使用。ElasticJob-Lite架構如下:
二者的api一樣只是部署方式有區別,後者是針對微服務方式部署的
Elastic-Job是基於quartz開發的,並藉助zookeeper實現動態擴容縮容,故障轉移,分片策略等。
在介紹elastic-job之前首先思考一個問題:為什麼要分散式定時任務?單機定時任務有哪些侷限性?分散式定時任務解決了什麼問題?
單機定時任務:
- 隨著服務流量的增加,分散式架構應運而生,如果現在有一個服務需要定時的給使用者推送訊息,但是打到這個服務上的流量又特別多,導致服務特別容易掛,首先想到的是橫向增加節點緩解伺服器壓力,但是與此同時多個節點也會同時推送多條訊息,所以想到把所有定時任務抽離出來,建立一個專門執行定時任務的服務,但是這個服務也不能掛,所以考慮搭建主備架構,但是隨著任務的增加,任務量的增重,定時任務節點的能否經得住壓力的考驗?單機情況下如何做到定時任務不丟不重?
1.1 Elastic-job的主要功能
分散式排程協調
:在分散式環境中,任務能夠按指定的排程策略執行,並且能夠避免同一任務多例項重複執行豐富的排程策略
:基於成熟的定時任務作業框架Quartz cron表示式執行定時任務彈性擴容縮容
:當叢集中增加某一個例項,會重新分片為新例項分配任務;當叢集減少一個例項時,它所執行的任務能被轉移到別的例項來執行。故障轉移
:某例項在任務執行失敗後,會被轉移到其他例項執行錯過執行作業重新執行
:若因某種原因導致作業錯過執行,自動記錄錯過執行的作業,並在上次作業完成後自動觸發。支援並行排程
:支援任務分片,任務分片是指將一個任務分為多個小任務項在多個例項同時執行。高可擴充套件
:預留監聽器介面和自定義分片策略介面以實現多種的個性化定製功能。豐富的作業型別
:支援簡單定時任務,流式定時任務,以及指令碼型別的定時任務
1.2 彈性伸縮實現
ElasticJob的“elastic”主要體現在其彈性伸縮,其分散式彈性伸縮的實現如下描述:
- 第一臺伺服器上線觸發主伺服器選舉。主伺服器一旦下線,則重新觸發選舉,選舉過程中阻塞,只有主伺服器選舉完成,才會執行其他任務;
- 某作業伺服器上線時會自動將伺服器資訊註冊到註冊中心,下線時會自動更新伺服器狀態;
- 主節點選舉,伺服器上下線,分片總數變更均更新重新分片標記;
- 定時任務觸發時,如需重新分片,則透過主伺服器分片,分片過程中阻塞,分片結束後才可執行任務。如分片過程中主伺服器下線,則先選舉主伺服器,再分片;
- 透過上一項說明可知,為了維持作業執行時的穩定性,執行過程中只會標記分片狀態,不會重新分片。分片僅可能發生在下次任務觸發前;
- 每次分片都會按伺服器 IP 排序,保證分片結果不會產生較大波動;
- 實現失效轉移功能,在某臺伺服器執行完畢後主動抓取未分配的分片,並且在某臺伺服器下線後主動尋找可用的伺服器執行任務。
1.3 對比xxl-job
先說一下和xxl-job的相同點和區別:二者都是在基於quartz的基礎上二次開發,不同的點在於xxl-job使用quartz基於資料庫的分散式功能,伺服器超出一定數量會給資料庫造成一定的壓力(排程中心透過獲取DB鎖來保證叢集中執行任務的唯一性,如果短任務很多,隨著排程中心叢集數量增加,那麼資料庫的鎖競爭會比較大,效能會隨之降低。)
而Elastic-Job是基於zookeeper實現分散式,也就是基於記憶體的分散式排程,利用zk的臨時節點和 watch 機制來實現節點上線下線對應的動作,並且zk底層採用nio、多執行緒模型,所以它的效能非常高。
2 ElasticJob的功能及應用
elasticjob內建zkclient,採用zk作為分散式協調註冊中心,利用zk的全域性一致性,以及具備的監聽和反向通知的功能,透過在zkServer上寫入永久/臨時節點,實現定時任務的分片、主節點選舉、註冊資訊等。下圖為zk客戶端顯示elasticjob註冊3臺例項:
2.1 分片
分片是指一個任務可以由多個例項執行。elastic-job自帶三種分片策略,分別是平均分片、根據作業名的雜湊值奇偶性分片、根據作業名的輪詢分片。預設平均分片。提供介面實現自定義分片。
分片可以實現什麼?
如果想要將作業分治提高效率,例如2w條資料同步,可以將資料同步任務分為4片,以提高效率增加容錯,並且,當你的節點數小於分片數時,任務也不會停止——轉而由多執行緒執行代替節點執行(高可用);如果想要提高某個定時任務的高可用性,那麼可以將任務分為1片,並啟動多個例項,這樣就可以在leader當機時,重新選舉並繼續執行任務,節點重新上線時會加入到備用節點。
自定義分片可以實現什麼?
就近排程。假設現在有兩個機房,A機房作為主資料來源,B機房全部作為備用節點,如果所有任務都在B機房被排程了,那麼這些資料都會跨機房寫入A機房,這樣延遲就大大提升了。自定義分片可以解決:根據主節點ip白名單,分片時寫入任務優先分配給主機房執行,當主機房全部當機啟用備用機房執行。
同城雙活。雙機房的架構進一步實現了高可用——防止機房斷電帶來的災難性損失。但是這種模式存在一個問題就是大部分時間B機房都是空閒狀態,極大浪費資源的同時,對於B機房可能存在的一些問題我們是不清楚的,例如資料庫許可權。沒有流量的驗證,當真正出現容災問題我們不能保證B機房一定可用。因此在此基礎上更進一步地實現同城雙活:即在備用機房上承擔一部分流量,如分片時根據任務列表把所有隻讀任務優先分配到備用機房,或是將任務列表末尾的1/10分配給B機房。
2.2 監聽器
elastic-job提供監聽器介面以實現執行任務前後的預處理和後處理。監聽器分為本地監聽器和分散式監聽器,本地監聽器會在每一個分片執行前後都觸發;分散式監聽器會在所有分片執行前後分別觸發。
本地監聽器:
分散式監聽器:
在版本號 <= 2.1.5之前的,此功能會出現分散式執行bug,導致多分片同時觸發監聽器,並沒有達到”只執行一次”的目的,2.1.7的版本解決方案為:
3.0.3解決方案為:先為所有分片建立guarantee節點,當所有節點都標記為啟動後,呼叫leader節點執行doBefore/doAfter,並加上guarantee下的zk鎖
2.3 失效轉移
ElasticJob 不會在執行過程中進行重新分片,而是等待下次排程之前才開啟重新分片流程。當作業執行過程中伺服器當機,失效轉移允許將該次未完成的任務在另一作業節點上補償執行。例如作業執行間隔1小時,執行時間30分鐘:
圖中表示作業分別於 12:00,13:00 和 14:00 執行。圖中顯示的當前時間點為 13:00 的作業執行中。
如果作業的其中一個分片伺服器在 13:10 的時候當機,那麼剩餘的 20 分鐘應該處理的業務未得到執行,並且需要在 14:00 時才能再次開始執行下一次作業。也就是說,在不開啟失效轉移的情況下,位於該分片的作業有 50 分鐘空檔期。如下如圖所示。
在開啟失效轉移功能之後,ElasticJob 的其他伺服器能夠在感知到當機的作業伺服器之後,補償執行該分片作業。如下圖所示。
資源充足的情況下,作業仍然能在13:30執行完成。
兩種執行機制
-
通知執行
當其他伺服器感知到有失效轉移的作業需要處理時,且該作業伺服器已經完成了本次任務,則會實時的拉取待失效轉移的分片項,並開始補償執行。也稱為實時執行。
-
問詢執行
作業服務在本次任務執行結束後,會向註冊中心問詢待執行的失效轉移分片項,如果有,則開始補償執行。也稱為非同步執行。
適用場景:
開啟失效轉移功能,ElasticJob 會監控作業每一分片的執行狀態,並將其寫入註冊中心,供其他節點感知。
在一次執行耗時較長且間隔較長的作業場景,失效轉移是提升作業執行實時性的有效手段;對於間隔較短的作業,會產生大量與註冊中心的網路通訊,對叢集的效能產生影響。而且間隔較短的作業並未見得關注單次作業的實時性,可以透過下次作業執行的重分片使所有的分片正確執行,因此不建議短間隔作業開啟失效轉移。
另外需要注意的是,作業本身的冪等性,是保證失效轉移正確性的前提。
2.4 錯過重觸發
是指使逾期未執行的作業在之前作業執行完成之後立即執行。舉例說明,若作業以每小時為間隔執行,每次執行耗時 30 分鐘。如下如圖所示。
圖中表示作業分別於 12:00,13:00 和 14:00 執行。圖中顯示的當前時間點為 13:00 的作業執行中。
如果 12:00 開始執行的作業在 13:10 才執行完畢,那麼本該由 13:00 觸發的作業則錯過了觸發時間,需要等待至 14:00 的下次作業觸發。如下圖所示。
在開啟錯過任務重執行功能之後,ElasticJob 將會在上次作業執行完畢後,立刻觸發執行錯過的作業。在 13:00 和 14:00 之間錯過的作業將會重新執行。如下圖所示。
適用場景:在一次執行耗時較長且間隔較長的作業場景,錯過任務重執行是提升作業執行實時性的有效手段;對於未見得關注單次作業的實時性的短間隔的作業來說,開啟錯過任務重執行並無必要。
2.5 錯誤處理策略
提供了一些第三方接入的錯誤報警的介面:企微、釘釘、郵件
2.6 執行緒池策略
此策略可能導致的問題:
-
問題:
專案分片分了4片,但是測試起來之後只有兩片在執行。
-
原因:
由於專案是在容器中執行的,在分配的時候只分配了一個虛擬CPU,在配置每個任務的時候是有一個配置是jobExecutorServiceHandlerType來配置每個任務的執行緒池處理策略,預設就是CPU的處理策略(對應實現類就是CPUUsageJobExecutorServiceHandler),每個任務是線上程池中執行的,執行緒池的分配又是根據CPU的核心數來決定(處理器數量的2倍),所以實際上容器中執行的時候拿的是虛擬的處理器數量,只有一個,執行緒池初始和最大的容量就是2,但是分片有4片,而且還在執行,所以剩餘兩片一直等待,沒有執行導致的問題。
-
解決:
擴充套件SPI,檔名就是原始碼中介面的引用
2.7 事件追蹤
ElasticJob 提供了事件追蹤功能,可透過事件訂閱的方式處理排程過程的重要事件,用於查詢、統計和監控。目前提供了基於關係型資料庫的事件訂閱方式記錄事件,開發者也可以透過 SPI 自行擴充套件。
JOB_EXECUTION_LOG 記錄每次作業的執行歷史。分為兩個步驟:
- 作業開始執行時向資料庫插入資料,除 failure_cause 和 complete_time 外的其他欄位均不為空。
- 作業完成執行時向資料庫更新資料,更新 is_success, complete_time 和 failure_cause(如果作業執行失敗)。
JOB_EXECUTION_LOG表:
JOB_STATUS_TRACE_LOG表:
2.8 可操作API
Elasticjob提供了JavaAPI,可以透過直接對註冊中心進行操作的方式控制作業在分散式環境下的生命週期。根據功能和職責的不同,這些api又分為了配置類API、操作類API、操作分片的API、作業統計API、伺服器狀態展示API和作業分片狀態展示API,這些類都在org.apache.shardingsphere.elasticjob.lite.lifecycle.api這個包下,便於開發人員擴充套件,我們可以將這些API暴露出來,抽成RESTful介面便於我們在作業執行時動態的控制它的生命週期和修改它的執行引數。
更多api不再列舉,詳情可以查閱官方文件。
2.9 視覺化
elastic-job提供視覺化UI,可以修改作業、操作節點、監控全域性連線、軌跡追蹤等。
作業維度:在作業維度可以對單個作業手動觸發、禁止、下線、修改作業配置。
修改作業:
服務維度:在服務可以針對某個節點禁止或是下線
軌跡追蹤:可以連線事件追蹤的資料庫,展示歷史軌跡和歷史狀態資訊
參考文獻:
本部落格內容僅供個人學習使用,禁止用於商業用途。轉載需註明出處並連結至原文。