輕松構建微服務之分布式任務調度


微信公眾號:內核小王子
關注可了解更多關於數據庫,JVM內核相關的知識;
如果你有任何疑問也可以加我pigpdong[^1]

前言

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

關鍵詞解釋

  • 管理后台:一個后台管理系統,提供web頁面供業務人員對任務參數和執行模式進行編輯,查看任務進度和任務結果
  • 執行器:提供任務具體執行的環境,一般指代應用本身,應用本身作為一個執行器,內部可以有多個待執行的任務單元
  • 任務單元:需要定時執行的任務單元,依賴具體業務場景,例如基金行業的日切任務計算每日收益
  • 調度器:依托特定調度的算法,例如每日早上8點,每間隔5分鍾等去喚醒執行器去執行指定的任務單元

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

  • 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.執行器注冊

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

2.任務單元的注冊

    如果任務單元不自動注冊,那么我們每次上線需要手工在管理后台編輯新增任務,並且選擇對應的執行器列表,如果任務單元也可以自己向調度中心注冊,我們的使用將更加簡單,我們可以規定我們的任務單元都實現一個指定的接口,或者加一個注解,更推薦用注解的形式,我們可以通過在注解上可以配置一些調度策略,否則的話這些調度策略只能在接口中體現了,而我們只需要在應用啟動的時候將這些接口和注解掃描出來發給注冊中心就好了,由於我們的執行器都是分布式部署的,所以同一個任務單元隨着應用的重啟會向注冊中心注冊多次,而注冊中心需要根據任務單元的特性進行防重復設計,而任務單元在代碼廢除后,我們可以手工在調度器進行刪除,或者先在注解上加一個參數,之后在下一個版本中刪除

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頂上


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM