分布式任务调度:你知道和不知道的事
點擊上方“服務端思維”,選擇“設為星標”
回復”669“獲取獨家整理的精選資料集
回復”加群“加入全國服務端高端社群「后端圈」
作者 |?崔凱
出品?|?騰訊云中間件
導語
對于定時任務大家應該都不會陌生,從骨灰級別的Crontab到Spring Task,從QuartZ到xxl-job,隨著業務場景越來越多樣復雜,定時任務框架也在不斷的升級進化。
那么今天就來跟大家從以下三個方面聊一聊分布式任務調度:從單機定時任務到分布式任務調度平臺的演進過程、騰訊云分布式任務調度平臺TCT是如何應運而生的、TCT具體落地案例情況和解決了哪些核心問題。
場景類型
定時任務的場景千千萬,但它的本質模型是個啥,怎么來理解定時任務呢,此處好有一比。
定時任務其實就是老師給學生布置作業,比如:
每天晚上7點準時開始寫作業,寫完了讓家長檢查簽字。
“每天晚上7點準時”是對時間精度和周期性的要求,是7點不是8點,是每天不是每周;“寫作業”是對任務執行內容的明確,是寫作業不是看奧特曼;“寫完了讓家長檢查簽字”使得“寫作業”和“家長檢查簽字”可以解耦為兩個邏輯動作,在孩子寫作業的時候家長還能看個看書啥的。
言歸正傳,定時任務的典型落地場景在各行業中非常普遍:電商中定時開啟促銷活動入口、15天未確認收貨則自動確認收貨、定點掃描未付款訂單進行短信提醒等;金融保險行業中也有營銷人員傭金計算、終端營銷報表制作、組織關系定時同步、日清月清結算等場景。總結下來,筆者按照“時間驅動、批量處理、異步解耦”三個維度來劃分定時任務的場景類型。
時間驅動型
以電商場景中定時開啟活動入口為例,一般情況會在后臺配置好活動需要的各種參數,同時將活動狀態的動態配置設置為關閉,當到達執行時間后定時任務自動觸發后開啟促銷活動。
可見,在時間驅動型場景中,相比執行內容而言,業務更關注的是任務是定時執行還是周期執行、執行具體時間點準確性、周期頻率的長短等時間因素。
批量處理型
批量處理型任務的特點在于需要`同時對大量`積累的業務對象進行處理。此時,可能有的朋友會問,為什么不使用消息隊列處理?原因是某些特定的場景下,消息隊列并不能夠進行簡單替代,因為消息隊列更多的是通過每個消息進行事件驅動,偏向更實時的處理。
以保險中傭金結算業務說明,比如營銷人員的傭金計算。營銷人員會從投保人繳納的保費中獲得一定比例的提成,并且這個比例會根據投保年限、險種的不同而變化,另外可能還會疊加公司的一些傭金激勵政策等。這樣的場景就需要積累一定量的數據之后,定時的進行批量計算,而不能每個事件都進行計算。
異步解耦型
說到系統的異步解耦一定又會想到消息隊列,但消息隊列并不能適用某些外部系統數據的獲取,比如證券行業中股票軟件公司對于交易所股票價格的抓取,由于股票價格對于股票軟件公司是外部數據,使用消息隊列是很難進行內外部系統間異步通訊的。所以,一般情況會通過批處理任務定時抓取數據并存儲,然后后端系統再對數據進行分析整理,使得外部數據獲取和內部數據分析處理兩部分邏輯解耦。
前世今生
單機定時任務
單機定時任務是最常見的,也是比較傳統的任務執行方式,比如linux內置的Crontab。其通過cron表達式中分、時、日、月、周五種時間維度,實現單機定時任務的執行。
# 每晚的21:30重啟smb 30 21 * * * /etc/init.d/smb restart另外,在java中也有內置的定時任務,比如java.util.Timer類和它的升級版ScheduledThreadPoolExecutor,另外在Spring體系中也提供了Spring Task這種通過注解快速實現支持cron表達式的單機定時任務框架。
@EnableScheduling @Service public class ScheduledConsumerDemo {@Value("${consumer.request.echoUrl}")private String echoUrl;/*** 間隔1秒請求provider的echo接口** @throws InterruptedException*/@Scheduled(fixedDelayString = "${consumer.auto.test.interval:1000}")public void callProviderPer1Sec() throws InterruptedException {String response = restTemplate.getForObject(echoUrl, String.class);} }顯而易見的,單機定時任務在應對簡單的業務場景是很方便的,但在分布式架構已然成為趨勢的現在,單機定時任務并不能滿足企業級生產以及工業化場景的訴求,主要體現在集群任務配置統一管理、單點故障及單點性能、節點間任務的通訊及協調、任務執行數據匯總等方面。為了滿足企業級生產的訴求,各類任務調度平臺逐步興起。
中心化調度
典型的中心化調度框架quartz,其作為任務調度界的前輩和帶頭大哥,通過優秀的調度能力、豐富的API接口、Spring集成性好等優點,使其一度成為任務調度的代名詞。
quartz架構中使用數據庫鎖保障多節點任務執行時的唯一性,解決了單點故障的問題。但數據庫鎖的集中性也產生了嚴重的性能問題,比如大批量任務場景下,數據庫成為了業務整體調度的性能瓶頸,同時在應用側還會造成部分資源的等待閑置,另外還做不到任務的并行分片。
另一款出自大眾點評的框架xxl-job,主要特點在于簡單、易集成、有可視化控制臺,相比quartz主要差異在于:
自研調度模塊:xxl-job將調度模塊和任務模塊解耦的異步化設計,解決了調度任務邏輯偏重時,調度系統性能大大降低的問題。其中,調度模塊主要負責任務參數的解析及調用發起,任務模塊則負責任務內容的執行,同時異步調度隊列和異步執行隊列的優化,使得有限的線程資源也可支撐一定量的job并發。
調度優化:通過調度線程池、并行調度的方式,極大減小了調度阻塞的概率,同時提高了調度系統的承載量。
高可用保障:調度中心的數據庫中會保存任務信息、調度歷史、調度日志、節點注冊信息等,通過MySQL保證數據的持久化和高可用。任務節點的故障轉移(failover)模式和心跳檢測,也會動態感知每個執行節點的狀態。
但由于xxl-job使用了跟quartz類似的數據庫鎖機制,所以同樣不能避免數據庫成為性能瓶頸以及中心化帶來的其它問題。
去中心化調度
為了解決中心化調度存在的各種問題,國內開源框架也是八仙過海、盡顯神通,比如口碑還不錯的powerjob、當當的elastic-job、唯品會的saturn。saturn整體上是基于開源的elastic-job進行改進優化的,所以本文只針對powerjob和elastic-job做簡要介紹。
powerjob誕生于2020年4月,其中包含了一些比較新的思路和元素,比如支持基于MapReduce的分布式計算、動態熱加載Spring容器等。在功能上,多任務工作流編排、MapReduce執行模式、延遲執行是亮點,同時宣稱所有組件都支持水平擴展,其核心組件說明如下:
powerjob-server:調度中心,統一部署,負責任務調度和管理;
powerjob-worker:執行器,提供單機執行、廣播執行和分布式計算;
powerjob-client:可選組件,OpenAPI客戶端。
powerjob在解決中心化調度時的無鎖調度設計思路值得借鑒,核心邏輯是通過appName作為業務應用分組的key,將powerjob-server和powerjob-worker以分組key進行邏輯綁定,即確保每個powerjob-worker集群在運行時只會連接到一臺powerjob-server,這樣就不需要鎖機制來防止任務被多臺server同時拿到,從而造成重復執行的問題。
雖然powerjob在各方面分析下來相對優秀,但畢竟產品迭代周期比較短,仍需要通過市場大規模應用來不斷打磨產品細節,以驗證產品的性能、易用性和穩定性。
elasticjob包含elasticjob-lite和elasticjob-cloud兩個獨立子項目,本文主要以elasticjob-lite為例展開。
elasticjob-lite定位為輕量級無中心化解決方案,在繼承quartz的基礎上,同時使用了zookeeper作為注冊中心。在產品設計層面上,個人理解elasticjob相比其他分布式任務調度框架,更加側重數據處理和計算,主要體現在如下兩方面:
elasticjob-lite的無中心化:
沒有調度中心的設計,在業務程序引入elasticjob的jar包后,由jar包進行任務的調度、狀態通訊、日志落盤等操作。
每個任務節點間都是對等的,會在zookeeper中注冊任務相關的信息(任務名稱、對等實例列表、執行策略等),同時依賴zookeeper的選舉機制進行執行實例的選舉。
elasticjob-lite的彈性分片:
基于zookeeper,任務執行實例之間可以近乎實時的感知到對方的上下線狀態,使得任務分片的分配可以隨著任務實例數量的調整而調整,并且保證負載相對均勻。
在任務實例上下線時,并不會影響當前的任務,會在下次任務調度的時候重新分片,以避免任務的重復執行。
通過上述分析,elasticjob更多的是針對分布式任務計算場景設計,更適合做大量數據的分片計算或處理,尤其對資源利用率有要求的場景下更有優勢。
演進過程
在粗略的介紹了各個主流的分布式任務調度框架后,一個問題出現了:是哪些主要因素推動了框架一步步發展演進?筆者簡要概括為如下4個因素:
業務復雜性:原先的業務復雜性低,2、3行代碼就可以搞定;隨著業務復雜性提高,任務的組織形態和執行內容都發生了很大變化,逐步衍生出任務編排、框架生態融合、多語言及多終端支持等訴求。
場景多樣性:不再僅僅是簡單的定時任務執行,類似批量計算、業務解耦等場景的問題,也逐步開始使用分布式任務調度框架解決。對框架能力的要求在于,更豐富的任務執行策略、動態分片計算的支持、豐富的任務治理能力等方面上。
分布式架構:分布式架構趨勢的全面到來,是最重要的推進因素??蚣艿恼w設計須以分布式架構為前提,在任務節點及調度中心間的通訊、調度平臺的高可用、任務節點的故障處理及恢復、任務調度可視化運維等方面,都是全新的挑戰。
海量數據并發:當海量的業務數據及并發調用成為常態,就使得分布式任務調度平臺需要在執行器性能、執行時間精準度、任務的并行及異步處理、節點資源彈性管控等方面推進優化,以幫助提升平臺整體的吞吐量。
分布式任務調度框架的演進,是業務系統從單體架構向分布式架構演進的一個分支。分布式任務調度平臺能力的不斷完善,與業務架構的微服務化演進不可分割。
同理,目前各行業的業務系統逐步遷移上云,企業數字化轉型趨勢明顯,未來分布式任務調度平臺的演進過程同樣離不開云原生產業環境,平臺的整體架構需要深度融合云原生體系,才能滿足未來多方面不斷變化的產業訴求。
“云上”的TCT
分布式任務調度服務(Tencent Cloud Task)是騰訊云自主研發的一款輕量級、高可靠的分布式任務調度平臺。通過指定時間規則,嚴格觸發調度任務,保障調度任務的可靠、有序執行。該服務支持國際通用的時間表達式、執行生命周期管理,解決傳統定時調度任務的單點故障及可視化程度低等問題。同時,支持任務分片、工作流編排等復雜調度任務處理能力,覆蓋廣泛的任務調度應用場景,如數據備份、日志切分、運維監控、金融日切等。
功能介紹
TCT在功能方面主要分為三個部分:調度管理平臺、任務調度服務、開發集成(SDK)。調度管理平臺提供優雅的可視化界面交互,任務調度服務實現分布式場景下的任務調度,開發集成深度融合開源框架,其中詳細功能特點說明如下
豐富的任務配置
多種執行方式:支持隨機節點、廣播、分片執行方式,滿足不同應用場景。
多種觸發策略:支持定時觸發、周期觸發、工作流觸發、人工手動觸發策略。
完善的容錯機制:支持異常重試、超時中斷、手動停止等多種任務容錯保護機制。
可視化的任務管理
任務管理視圖:展示任務的執行狀態,提供新增任務、編輯任務、刪除任務、手動執行、啟動/停用任務等操作能力。
執行記錄視圖:展示所有常規任務、工作流任務的執行批次詳情列表,支持依據所屬任務、部署組為查詢過濾條件。
執行列表視圖:展示選定任務的執行批次詳情列表,支持針對任務批次的停止、重新執行操作。
執行詳情視圖:展示任務執行批次的執行實例列表,支持針對執行實例的停止、重新執行、日志查詢操作。
工作流管理視圖:展示工作流任務的執行狀態,提供工作流任務新建、可視化流程編排、啟動/停用工作流任務等操作能力。
完善的任務運行監控告警
立體化監控:提供任務運行狀態、任務執行批次狀態、執行實例運行狀態的立體化監控,支持針對執行實例的線上日志查看能力。
靈活告警策略:集成云監控能力提供任務執行批次、執行實例異常告警,工作流任務執行批次、批次任務、執行實例異常告警能力,支持靈活的指標告警及事件告警配置。
架構原理
TCT各組件簡介如下:
觸發器:解析任務的觸發規則
調度器:派發需要執行的任務、管理任務狀態等
監控:任務執行相關的監控數據上報
控制臺:管理員的控制臺界面
接入層:任務下發、狀態上報等消息的信道管理器
接入網關:統一對接接入層及SDK的網關
SDK:和業務進程運行在一起,負責執行任務中定義的一段具體代碼邏輯
首先,由觸發器解析用戶在控制臺配置并存入DB的任務信息,并將解析后的執行信息投入到MQ中。其次,由調度器消費執行信息并通過接入層下發到具體的執行器節點上(接入層中有具體的節點注冊信息,包括IP地址等)。最后,當SDK所在節點完成任務的執行后(成功、失敗、未響應等),會將執行結果通過TCP長連接傳回給調度器,然后調度器會跟DB進行交互完成任務狀態的變更,同時上報任務執行情況到監控模塊。
通過功能簡介可以發現TCT基本涵蓋了常見的任務調度場景中所需功能,尤其在可視化視圖方面做了大量的工作,同時依托騰訊云完備的基礎設施建設,在高可用保障和減少運維成本方面也提供了極大保障。此外,TCT源于TSF技術平臺,對TSF應用天然集成,支撐組件可以很方便的獲取TSF應用的相關信息,如TSF部署組ID、節點IP、應用ID等,因此在任務執行效率上也會更高。
不過,由整體架構圖發現TCT采用中心化調度方案,調度器、觸發器及控制臺組件無狀態,支持水平擴展,組件及SDK間通過TCP長連接通訊;而數據流依賴DB及MQ,在任務數量大、執行頻率高的大規模落地場景下,DB和MQ的吞吐量就會成為性能卡點,即使可以優化也會有明顯上限。所以根據目前TCT的產品形態,其更多的適用于輕量級任務調度場景。
分片執行案例
背景概述
分片執行模式是大批量數據處理場景下經常用到的執行方式,本案例以保險行業中子公司每天向總公司匯總當天營銷數據的業務場景為例進行說明。
從上圖可見,匯總營銷數據的服務(后文稱summarydata)每天凌晨2:00定時調用34個子公司提供的營銷數據查詢API。之所以使用分片執行方式,是因為匯總營銷數據的操作需要在同一時間觸發,且整體匯總時間越短越準確。此外,各公司的營銷數據量并不相同,而且即使是相同的子公司每天產生的營銷數據量也不相同。
配置步驟
根據如上業務背景描述,同時基于現有資源情況,整體配置思路為:
創建一個summarydata部署組,其中新建4個實例,單個實例線程池數量為3;
應用代碼中將34個子公司一一對應到1~34的公司ID上;
根據大致地域和日均產生的數據量,將34家公司劃分到NORTH、SOUTH、EAST、WEST四個區域;
分片數量為4,每個分片對應到1個實例,即1個實例至少計算1個區域的數據;
每個區域key對應的子公司ID列表可通過代碼配置進行半自動調整,防止某個子公司數據量陡增情況;
為防止統計重復,不配置任務自動重試,采用手動補償。
步驟一:觸發類代碼編寫并打包
java public class SimpleShardExecutableTask implements ExecutableTask {private final static Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());@Overridepublic ProcessResult execute(ExecutableTaskData executableTaskData) {// 輸出任務執行元數據TaskExecuteMeta executeMeta = executableTaskData.getTaskMeta();LOG.info("executeMetaJson:{}",executeMeta.toString());// 輸出分配給本實例的分片參數ShardingArgs shardingArgs = executableTaskData.getShardingArgs();LOG.info("ShardCount: {}", shardingArgs.getShardCount());Integer shardingKey = shardingArgs.getShardKey();LOG.info("shardingKey: {}", shardingKey);String shardingValue = shardingArgs.getShardValue();LOG.info("shardingValue: {}", shardingValue);// 模擬任務執行try {this.doProcess(shardingValue);} catch (Exception e) {e.printStackTrace();}return ProcessResult.newSuccessResult();}public void doProcess(String shardingValue) throws Exception {if (shardingValue.equals(CompanyMap.NORTH.area)){Arrays.stream(CompanyMap.NORTH.companyIds).forEach(companyId->LOG.info("calling north subsidiary_{} api.....",companyId));} else if(shardingValue.equals(CompanyMap.SOUTH.area)){Arrays.stream(CompanyMap.SOUTH.companyIds).forEach(companyId->LOG.info("calling south subsidiary_{} api.....",companyId));} else if(shardingValue.equals(CompanyMap.EAST.area)){Arrays.stream(CompanyMap.EAST.companyIds).forEach(companyId->LOG.info("calling east subsidiary_{} api.....",companyId));} else if(shardingValue.equals(CompanyMap.WEST.area)){Arrays.stream(CompanyMap.WEST.companyIds).forEach(companyId->LOG.info("calling west subsidiary_{} api.....",companyId));} else {throw new Exception("input shardingValue error!");}ThreadUtils.waitMs(3000L);}enum CompanyMap{NORTH("NORTH", new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9}),SOUTH("SOUTH",new int[]{10,11,12,13,14,15,16,17,18,19}),EAST("EAST",new int[]{20,21,22,23,24,25,26,27,28}),WEST("WEST",new int[]{29,30,31,32,33,34});private String area;private int[] companyIds;CompanyMap(String key,int[] values){this.area = key;this.companyIds = values;}public String getArea() { return area; }public void setArea(String area) { this.area = area; }public int[] getCompanyIds() { return companyIds; }public void setCompanyIds(int[] companyIds) { this.companyIds = companyIds; }} }步驟二:創建應用及部署組,并完成部署
步驟三:創建TCT任務
步驟四:手動啟動任務測試
測試效果
通過控制臺查看實例執行情況,同時可通過分片參數按鈕,查詢某個實例執行批次內的分片參數。
通過應用日志查看結果,可發現有1個實例運行了2個分片任務,是由于TCT對實例負載情況進行了判斷,選擇了相對空閑的實例。
此外還進行了服務內實例異常的測試,即當summarydata服務中4個實例僅余1個實例正常時任務的執行情況(由于日志較長,筆者節選了重要部分)??梢钥吹角?個分片任務是同時且使用不同線程執行的,第4個分片任務是在前3個任務執行完成后再執行的,符合預期。
未來方向
分布式任務調度平臺框架間的競爭過程漫長而膠著,各家廠商都在尋求產品價值上的突破口,TCT也仍有很多不足,需要從市場需求和技術趨勢的角度持續深度思考。針對分布式任務調度市場,筆者粗略總結了如下幾點未來產品可能的優化方向:
1.去中心化
中心化的分布式任務調度平臺缺點明顯,難以支撐企業大規模落地場景。同時,市場中的產品及技術演進趨勢逐漸向去中心化發展,原因在于去中心化的分布式任務調度平臺才具有大規模商業化落地的可能,成功的商業化落地案例也是產品走向成熟的標志。
2. 容器化
分布式任務調度平臺組件及組件間通訊目前多為傳統虛機方式,如果能同時實現支撐組件的容器化部署,就可以更好的發揮容器平臺快速啟停、資源調度、水平擴展等方面的優勢,以提高支撐側整體可用性,減少擴縮容時的運維成本,有效提升平臺整體的吞吐量,而高可用、彈性擴縮、高性能是大型企業數字化轉型云原生的重要考量因素。
3. 可編程
越來越多的分布式任務場景需要針對多個任務做復雜的任務編排,目前主流的編排仍局限于任務間串行、并行、與或等簡單的邏輯處理。未來更多的需要一種通用的、可編程的模板語言,用于描述任務參數及內容、DAGS(有向無環圖)、操作符、觸發動作等,標準化各家廠商對于任務編排的定義方式。
4. 容錯補償
在任務及工作流執行異常時的處理策略也有很多方面需要完善,比如由于實例夯死導致的過時觸發問題、任務追趕和任務堆積問題、工作流場景下任務異常后是整體重試還是斷點續傳重試等。
5. 場景升級
目前各家產品在常見的定時任務場景中,功能同質化程度比較高。但隨著云原生、大數據等相關領域的快速發展,分布式任務調度平臺也逐漸產生了新的應用場景,如大數據場景下的分布式計算及計算匯總、調度平臺對接serverless應用等,這都對產品的場景和功能提出更高的要求。
尾聲
通過對定時任務的場景、演進歷史、各平臺框架的介紹以及騰訊云自研分布式任務調度框架TCT的實踐案例描述,筆者繼前人的基礎上對分布式任務調度框架的應用現狀和未來發展進行了簡要分析,之前知道的和不知道的現在讀者朋友應該都知道了。謹希望本文能在技術選型及開源建設方面提供些許思路和視角,供企業和開源社區參考。
— 本文結束 —
●?漫談設計模式在 Spring 框架中的良好實踐
●?顛覆微服務認知:深入思考微服務的七個主流觀點
●?人人都是 API 設計者
●?一文講透微服務下如何保證事務的一致性
●?要黑盒測試微服務內部服務間調用,我該如何實現?
關注我,回復 「加群」 加入各種主題討論群。
對「服務端思維」有期待,請在文末點個在看
喜歡這篇文章,歡迎轉發、分享朋友圈
在看點這里
總結
以上是生活随笔為你收集整理的分布式任务调度:你知道和不知道的事的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java格林尼治时间_Java日期时间使
- 下一篇: 工商管理专业知识与实务(初级)【10】