輕鬆構建微服務之分佈式任務調度

微信公衆號:內核小王子
關注可瞭解更多關於數據庫,JVM內核相關的知識;
若是你有任何疑問也能夠加我pigpdong[^1]java

前言

     咱們在應用開發的時候,應該都碰到過這種需求:天天固定時間點跑一個任務;建立一些臨時的任務去初始化數據或者作數據遷移;固定一個時間週期去輪詢是否有新的狀態發生;在java中有兩個類能夠幫咱們處理這種需求,一個是java.util.TimerTask,一個是 java.util.concurrent.ScheduledExecutorService , 可是隨着業務的發展,任務調度的需求會愈來愈多,對調度器的要求也會更高,例如可以監控任務的執行進度,可以根據應用負載動態路由選擇更健康的執行器去執行任務,因此咱們須要一個系統將調度器和執行器分離開,在調度系統中增長更多功能來輔助咱們運維業務中的各類任務。web

關鍵詞解釋

  • 管理後臺:一個後臺管理系統,提供web頁面供業務人員對任務參數和執行模式進行編輯,查看任務進度和任務結果
  • 執行器:提供任務具體執行的環境,通常指代應用自己,應用自己做爲一個執行器,內部能夠有多個待執行的任務單元
  • 任務單元:須要定時執行的任務單元,依賴具體業務場景,例如基金行業的日切任務計算每日收益
  • 調度器:依託特定調度的算法,例如每日早上8點,每間隔5分鐘等去喚醒執行器去執行指定的任務單元

    咱們能夠思考一下,一個集中式任務調度系統須要哪些功能點,才能方便咱們對業務中的應用進行管理。redis

  • 1.任務管理,可以經過後臺管理頁面,新增,刪除任務,可以查看當前正在執行的任務進度和執行結果
  • 2.執行器可以在啓動和關閉的時候自動通知到調度中心,這樣在配置任務單元的時候就不須要指定IP地址去選擇對應的執行器了,執行器已經自動註冊到調度中心了
  • 3.執行器內部待執行的任務單元經過註解和接口的形式自動被掃描出來,而後向調度中心註冊,多個執行器的同一個任務能夠作到去重
  • 4.調度中心可以集羣部署,避免單點,以及分攤調度負載
  • 5.調度器能夠支持特定的路由算法來選擇執行器進行調度,例如隨機,輪詢,權重,指定等
  • 6.支持失敗重試,超時預警等功能,在檢測到任務執行器明確返回任務執行失敗後能夠選擇另一個執行成功率高的執行器從新執行
  • 7.支持郵件或者短信向業務人員報警的功能,核心的任務可能須要隨時進行監控,有些報表類任務單元在執行完成後輸出的報表也能夠經過郵件通知到相關業務
  • 8.支持是否容許同一個任務併發執行的模式,這樣能夠避免一個任務單元尚未執行結束,結果調度器又啓動了該任務單元,當任務單元沒法保證冪等的時候,調度中心能夠作下限制,能夠等上一個任務單元結束在執行
  • 9.任務依賴,一個任務執行完當即開始執行另一個關聯任務,或者校驗前置任務A沒有執行完不能執行後置任務B,這兩種狀況都是任務系統能夠考慮作的
  • 10.支持GLUE模式,能夠經過管理後臺動態上傳一段groove代碼,選擇執行器,在執行器內部轉換成任務單元而後執行,這種方式能夠在某些緊急狀況下能夠不用發佈就能夠修改線上數據,可是若是沒有相關的審計流程可能並不安全,
  • 11.任務分片,支持根據某種分片算法,例如日期間隔,用戶ID哈希等,將分片結果以參數的形式分發給多個執行器去併發執行,這樣能夠解決當一臺執行器去執行某個任務單元會執行太慢
  • 12.能夠經過管理後臺暫停或者取消一個正在執行的任務單元,這個在當這個任務是在批處理數據的狀況下發現以前處理的數據有錯誤能夠及時中止,減小影響範圍
  • 13.調度器高能夠

核心功能解析

    對於一個調度中心,咱們要解決的核心問題主要是兩個,有了這兩個功能那麼一個調度中心就基本可用,而其餘功能就是將這個系統打造的更易用,更完善
一個是調度器的實現,這個咱們能夠藉助業界經常使用的quartz框架,quartz也支持集羣部署。另一個是調度器如何經過rpc通知執行器去執行某個任務單元,並將執行參數傳輸過去,若是執行器是一個web容器,部署在tomcat中,那麼能夠在執行器中增長一個servlet來接受調度器的請求,在這個servlet內部去派發到對應的任務單元執行和取消,固然咱們也能夠藉助其餘的rpc框架,例如dubboo,grpc等,因爲調度器和執行器之間的調用比較簡單,咱們也能夠經過netty實現一個簡單rpc程序算法

1.執行器註冊spring

    當沒有註冊功能的時候,咱們須要手工編輯線上的執行器,而每次應用變動IP或者上下線都須要手工維護,若是不維護執行器咱們就沒法作路由,並且每次調度都須要輸入執行器地址。
註冊功能咱們能夠藉助zookeeper來實現,執行器也就是咱們的應用在啓動的時候能夠將相關信息寫入一個臨時節點,當執行器退出的時候這個臨時節點會自動刪除,而這些信息均可以通知到調度器,相似的註冊中心實現還有eureka,etcd等,加入這些註冊中心可能讓咱們的應用很重,執行器和調度器都須要依賴註冊中心的客戶端應用,而此時咱們只是須要將執行器相關信息在啓動和退出的時候發送給調度中心,咱們能夠在調度中心監聽一個http端口,執行器在啓動和退出的時候能夠直接經過httpp將信息發給調度中心,配合域名也很方便的能夠作調度中心的負載均衡,固然若是咱們調度器和執行器之間自己已經有RPC調用了,這些註冊信息也能夠經過rpc進行傳輸,執行器的啓動能夠在相關核心類的初始化方法中實現,執行器的關閉能夠藉助相關資源類的釋放或者JVM的關閉鉤子數據庫

2.任務單元的註冊緩存

    若是任務單元不自動註冊,那麼咱們每次上線須要手工在管理後臺編輯新增任務,而且選擇對應的執行器列表,若是任務單元也能夠本身向調度中心註冊,咱們的使用將更加簡單,咱們能夠規定咱們的任務單元都實現一個指定的接口,或者加一個註解,更推薦用註解的形式,咱們能夠經過在註解上能夠配置一些調度策略,不然的話這些調度策略只能在接口中體現了,而咱們只須要在應用啓動的時候將這些接口和註解掃描出來發給註冊中心就行了,因爲咱們的執行器都是分佈式部署的,因此同一個任務單元隨着應用的重啓會向註冊中心註冊屢次,而註冊中心須要根據任務單元的特性進行防重複設計,而任務單元在代碼廢除後,咱們能夠手工在調度器進行刪除,或者先在註解上加一個參數,以後在下一個版本中刪除tomcat

3.任務中斷安全

    通常咱們的任務單元在被調度的時候都會在一個線程工廠建立的特定線程裏面運行,而將正在執行的任務中斷咱們只能調用thread.interrupt()方法,而這個方法可能只能在這個線程wait的時候才能退出,咱們建議針對任務單元設計一個volatile變量標示任務是否終止,而在任務單元內部能夠循環遍歷該變量是否已經取消,這種方式當咱們的任務是處理一些批量數據,而且須要循環遍歷的時候相對優雅不少微信

4.任務調度模式

    路由策略,失敗重試,超時檢查,併發控制,任務依賴,這些咱們通常經過編寫特定的策略就能夠實現,關於分片,相似於數據庫的分庫分表,這種通常須要任務單元內部支持,而調度中心只是在上層輔助進行調度,選擇多少個執行器併發執行,將分片結果以參數形式分發給執行器

5.GLUE模式

    這個咱們須要藉助groove的classloader將傳人的代碼加載成JVM裏面的任務單元

下面咱們分析下開源的XXL-job的實現

    xxl-job 調度中心和執行器之間經過一個叫XXL-RPC的中間件進行雙向通信,調度中心經過RPC向執行器下發調度請求,執行器向調度中心註冊執行器信息,XXL-RPC是做者開源的一個機於protobuff協議實現的RPC,而調度器採用開源的quartz實現
調度器和應用端集成,須要經過配置文件來配置調度中心的地址,若是應用是在spring來管理bean的生命週期,能夠配置一個bean: XxlJobExecutor,並在屬性中設置調度中心的IP地址以及通信信息,並設置初始化方法init-method, 在init-method方法內部,會去初始化RPC相關類,而後將執行器註冊到調度中心,而後會掃描任務單元並緩存對應的bean實例。

image

調度器的高可用

若是調度器要集羣部署,有兩種模式

  • 1.主備模式

這種模式須要集羣中選一個master,其餘節點都爲slave節點,同一時刻只有一個調度器可以下發任務指令(全部節點在下發指令前判斷是不是master節點),由於咱們不容許同一個任務被多個調度器觸發執行形成job重複執行,當master節點掛了以後,其餘slave選舉出一個新的節點做爲master繼續提供服務,選取master的算法能夠利用paxos算法或者raft算法,在多個調度器節點之間進行選舉,而且經過心跳檢測來發現master節點掛了以後從新進行選舉,這類算法實現起來較爲複雜,咱們能夠利用zookeeper的客戶端在失去鏈接後會自動刪除這個客戶端建立的全部臨時節點,並能夠通知監聽程序來實現,全部的節點在啓動的時候都向zookeeper建立同一個節點,第一個建立成功的就是master節點,其餘節點發現已經存在該節點就自動做爲slave節點,並監聽這個節點的變化,當這個節點被刪除後,全部的slave節點在收到通知後在建立這個節點來爭master,這種方式實現起來比較簡單,可是調度器的可用性不能僅僅經過和zookeeper是否可以鏈接成功表示,有時候調度器若是鏈接不是數據庫也會
致使調度器不可用,這種狀況下能夠在調度器啓動一些監控程序,查看和數據庫的鏈接,cpu負載狀況,當達到閾值後主動讓出master節點

  • 2.多主模式

這種有點相似於redis集羣,當用上面的主備模式,若是被調動的任務單元數量上升會對調度器形成很大的壓力,那麼一個任務單元若是可以在多個調度器之間作隨機選擇一個被調度,這樣能夠減小調度器的壓力,當其中一個調度器掛了以後,這個調度器所管理的任務單元將有其餘調度器接管,
這種模式下咱們能夠把任務調度和任務管理拆分出來部署

  • 3.多主多備模式 相似於redis的集羣模式,將任務單元進行分片,每個master執行器管理一部分,每一個master執行器能夠有多個slave節點,當master節點掛掉後slave頂上
相關文章
相關標籤/搜索