
背景
在日常開發中會經常遇到一些需要異步定時執行的業務訴求,典型的使用場景如:超時未支付訂單關單、每隔 2h 更新好友排行榜、3.22 日 17 點《xx》劇上線等,目前業務側多基于以下思路來快速搭建一個調度系統,mysql 或者 redis 佇列存盤待執行任務,通過 crontab 定時觸發應用完成“撈取、計算、執行等操作”,不難看出存在幾類亟待解決問題:
1)缺少統一的調度平臺導致各業務重復開發;
2)簡易版調度實作在任務吞吐、調度時效上缺少保障;
3)業務和調度資料強耦合存盤給線上穩定性引入大 key、慢 sql 風險,
目前存在多類開源解決方案如 XXL-Job 、 Elastic-Job、quartz 調度等,但這些都屬于行程級調度平臺,很難滿足更細粒度的業務呼叫,基于上述的業務訴求和司內現狀,我們準備搭建一套通用的分布式任務調度平臺(以下統稱為 tjobs 平臺)以滿足業務高可靠、低延遲的海量任務調度訴求,

整體設計
設計目標
旨在提供一個易用、可靠、高性能、低時延的海量任務管理、調度平臺,幫助開發工程師專注于面向業務編碼設計,而不再擔心定時任務的吞吐量、可靠性等非功能需求,由此衍生的功能和非功能訴求分別為:
功能性訴求:
任務管理:包括任務注冊、任務啟停、任務更新等,
任務查詢:主要用于任務追蹤、問題排查、調度統計等,
任務回呼:由業務提供 spi 回呼實作,tjobs 平臺定時呼叫觸發
非功能性訴求
tjobs 定位為高可靠、高性能、低延遲、簡單易用的任務調度平臺,在滿足核心功能的基礎上提供以下非功能性保障:
平臺化:支持多業務接入、百億級任務注冊
易用性:自助化接入、運維,使用成本遠低自建
高可靠:全年 3 個 9 可用性、p99(時延)<1s
高性能:支持 100w+TPM 的任務觸發
多協議:支持多協議、組播、單播多種回呼方式
綜合看需要 tjobs 設計支持百億級任務量和百萬 TPM 并發執行,并在此基礎上滿足三個 SLA:
-
注冊\觸發可用性>99.95%
-
任務觸達率>99.99%
-
p99(觸達延時)<1s
設計思路

如上圖所示描述了對任務注冊、觸發流程的抽象,不難看出 tjobs 平臺為達成上述任務量級和三個 SLA,需要在海量資料存盤、高并發、觸發時效以及高可用上做出相應的設計保障,下面分別講述一下:
資料存盤:重點解決兩個問題資料可靠和海量存盤,可靠的存盤保障任務不丟、任務高觸達率,鑒于 mysql 在持久化以及 master-slave 部署架構對高可用支持表現,優先選用 mysql 作為底層存盤;但單 DB 在 TPS 性能、資料量上存在瓶頸,這里選用分庫分表策略,通過增加資料庫實體打平資料分布以提升整體性能和存盤上限;
實時性:類似多級快取的思路,為保障任務觸發時效(p99<1s)這里的設計思路“任務前置”,拆解任務觸發步驟,將任務撈取、計算作業盡量提前完成,通過毫秒級延遲的記憶體時間輪最終觸發,保障任務的觸發時效性;
高并發:采用可伸縮架構設計,存盤層盡量拆分為多個邏輯庫,前期通過合并部署降低成本但保留多個邏輯庫隔離能力,未來支持快速遷移獨立部署以提升性能;應用層采用多級調度思路,按資料分片將大任務拆分成小粒度任務動態根據計算節點數完成分配,實作通過增加計算節點快速提升任務觸發能力;
高可用:MTTR 分段治理思路,架構層在設計階段考慮到單點、單機房風險,不管是存盤層還是應用層都采用多機多活架構,并支持 HA 自動切換大大縮短 MTTF 時效;立體化的監控+撥測能力,覆寫從注冊到觸發全流程波動、成功率、耗時、延遲多維度監控,縮短 MTTI 時效;
整體流程

如上圖所示一個任務執行流程和生命周期,大概分成四個階段:
● 初始化:tjobs 提供任務注冊介面,完成任務校驗、計算并持久化到 mysql 存盤,業務根據實際場景選擇 CronCycleTask、IntervalCycleTask、FixedTimeSingleTask、DelayedTimeSingleTask 等不同任務型別提交注冊即可;
● 待執行:tjobs 會每隔 5min 執行一次,撈取未來 5min 內所有待執行的任務,注冊到一個記憶體 TimingWheels.中,由 timewheel 通過 callBackFunc 實作定時回呼從而實作毫秒級延遲觸發業務回呼;
● 執行中:首先會產生一條 init 狀態的調度流水、并根據任務型別、任務周期計算下一次調度時間,將 insert flow 和 update task 兩個操作合并到一個事務中更新到 DB,通過事務保證每次任務肯定能被調度到;
● 已觸發:根據 init flow 查找業務的回呼配置,支持 http、trpc、videopacket-jce 多種協議,支持單播、組播多種型別的回呼業務 spi,由業務完成回應的業務操作即完成了一次完整的任務調度,
詳細設計
領域模型

按照整體流程描述,tjobs 的主要職責管理好業務的定時任務調度,為此 tjobs 內部也需要會有一系列的跑批任務來保障調度的實時性,所以這里 tjobs 對兩類任務分別做了抽象,如上圖所示 tjobs 內部的跑批任務統稱為 job、業務定時調度任務稱為 task,tjobs 會將整個跑批任務拆分為 512 個最小的執行單元,按照當前可調度機器數打包成不同的 jobGroup 然后分發給 svr,由此衍生的幾個關鍵模型說明:
JobGroup:tjobs 內部分發調度和容災最小單元,會根據當前 svr 數量動態生成
Job:tjobs 任務最小執行單元,goroutine 協程調度單位(協程模型會詳細介紹作業)
JobParam:每個 job 批次執行時的輸入引數,批任務的執行模式類似 CyclicBarrier,每個周期有每個周期的執行引數
Task:業務注冊的定時調度任務,分周期任務、單次任務等(下圖以 cron 為例展示 19:01 時模型快照)

tjobs 的跑批任務的 timeline 原理如下圖所示,假設 tjobs 按照 cron(0 0/5 * * ?)執行,在 19:00 時發起調度會拉取 taskA-taskE 任務平均分配給當前可運行的 svr1-svr5 機器上,19.05 以此類推,當 19.10 調度時 svr4 宕機,這會將 taskN 任務和 taskO 任務分配到 svr5 上完成對 svr_4 的容災,

分庫分表
由于 redis 記憶體型存盤,在持久化、事務上保障不足導致生產環境很容易出現丟任務或重復調度的情況,所以本次底層存盤不在依賴 redis 存盤而選用 mysql 資料庫存盤,按照百萬 TPM 觸發和百億任務存盤設計要求需通過分庫分表來支持橫向擴展能力,
如下圖所示,我們生產環境部署了 8 個 DB 實體,每個實體上部署了 4 個邏輯庫(目前先通過合并部署減少成本,未來如有更高 TPS 訴求每個邏輯庫單獨部署即可),每個邏輯庫中拆分成 16 個表(拆分多表的目的是保障百億級任務存盤時單表行數不超過 2000w)以保障索引效率和查詢性能,

多級調度
解決了 DB 存盤的性能問題,接下來需要解決應用層單機性能限制,這里我們選擇“多級調度模型”,物理上充分利用多機資源通過多機并發執行突破單機并行執行緒的限制,最大化提升任務觸發的 TPS 上限,實作原理上將一個大的跑批任務拆解成多個小跑批任務分發到多臺機器上執行,可以將內部跑批任務分成兩個階段,階段一為 job 任務打包和派發、階段二為 job 任務撈取和執行,多級調度主要實作階段一,

如上圖所示,詳細的執行流程分成 4 個步驟:
-
基于定時調度平臺,每個 5min 做一次 cron 調度通知一臺 tjobs 機器
-
tjobs 通過名字服務查詢當前服務下所有可用機器供后續分包、調度
-
tjobs 根據當前可調度的機器數(n)將 512 個 job 打包成 n 個 jobGroup
-
將每個 jobGroup 系結到一個機器上,通過指定 ip 方式通知服務執行階段二(階段二的詳細實作見下節)
如領域模型中描述 tjobs 跑批任務采用 CyclicBarrier 柵格模式運行,這樣做的目的 1)、期望每個周期各個 job 都能完成所有待觸發任務(即 T1 周期完成 T1 時間之前所有的任務)防止任務積壓;2)、每個任務都以相同的執行周期和引數運行可以冪等,防止任務被重復調度,從平臺側盡力提供 only once 的觸發保障,
執行緒模型
本節接上節會詳細介紹一下階段二每個協程內單個 job 的詳細執行流程,如下圖所示會拆解 5 個步驟:
-
掃描本周期內所有待執行的任務,task 在注冊、執行后都會更新下次待執行時間
-
將掃描出來的任務按照待觸發時間注冊到 timingwheel 中(timingwheel 為秒級)
-
timingwheel 到指定時間觸發業務主要完成兩個操作:生成調度流水并更新 task 下次執行狀態 + 執行業務回呼
-
根據業務回呼配置(包括協議型別、回呼方式、超時時間、重試次數等),執行業務回呼通知
-
更新調度流水狀態,調度成功后或達到重試次數后推進流水到終態
tjobs 的跑批執行周期 5‘,業務 task 可能會按照 30''調度,這里會生成 10 個待執行任務注冊到 timingwheel 中,
通過 mysql 事務保障,生成流水和更新 task 下次執行狀態在一個事務內,保障任務肯定能被觸發到,
tjobs 會有兜底協程持續掃描未到終態的調度流水持續推進,保證任務觸達率>99.99%,
HA 支持
作為一個任務調度平臺,系統的高可用性和功能的完整性同樣重要,所以對外承諾三個核心 SLA(全年可用性>99.95%、任務觸達率>99.99%、p99(延遲)<1s),達成上述 SLA 就需要底層存盤、外部依賴均保持高可用外,應用自身架構需要有更強魯棒性,
DB 容災
DB 實體按照一主兩備部署,依賴 DB 持久化能力、以及主備半同步復制能力,存盤層在主庫故障時能自動 failover 到備庫且保證資料 rpo=0(不丟資料),能應對存盤層單機故障,同時兩個備庫分別部署到兩個可用區機房,從而支持同城跨機房災備能力(考慮成本問題暫不支持跨城容災),
因此從 DB 層看平臺的可用性 SLA 滿足>99.99%,并且任務 RPO=0 滿足不丟任務 SLA,主備切換分鐘級 RTO 基本滿足全年 P99(延遲)<1s 的 SLA,
應用容災
根據多機調度模型原理,每隔固定周期執行一次跑批任務,將未來待執行的任務快取到應用記憶體中由 timingwheel 觸發,其中涉及四個應用服務(定時調度、名字服務、資料庫和 tjobs 應用)協作,資料庫實體容災上節已分析基本滿足 SLA,名字通過增加本地快取實作弱依賴也能滿足 SLA,現需要對定時調度平臺和 tjobs 應用兩個強依賴服務做容災能力保障,
定時調度平臺不可用或調度延遲直接導致任務不能被準時調度,這里應對思路有:
-
依賴 linux 的 corntab 觸發,存在應用單點問題,導致整體可用性無法保障
-
基于調度平臺分鐘級 RTO,通過增大調度周期減少對調度平臺依賴度
為達成 p99 延遲<1s,tjobs 會提前將待觸發任務快取到應用記憶體中,這樣如果 tjobs 應用服務器宕機則該服務器上本周期內任務都不能被正常調度,只能等下個執行周期被重新撈起調度,導致 p99(任務延遲)<1s 不達標,這里應對思路有:
-
縮短調度周期(5'->30''),最多影響單機上 30‘’任務的調度延遲,降低延遲概率但不能徹底解決問題,且縮短周期會和調度平臺互動更強(有悖減少調度平臺依賴)
-
服務器支持主備 failover,每個任務組派發到多個服務器上,通過 etcd 選主一臺服務器執行,如果服務器宕機自動 failover 到備機執行,max 延遲就是選主耗時
綜合上述分析看,要提升保障平臺整體的 P99(延遲)、和 99.95%的可用性 SLA,最優方案是“基于調度平臺+應用服務器主備 failover”,具體的實作思路(如下圖所示),每個周期內待調度的 jobGroup 分被分配到三個不同應用 svr 上,應用層一主兩備的部署運行時,然后三個應用 svr 鏈接 etcd,利用 etcd 的選主和自動 failover 能力,既保障了任務運行的 only once 又能保障單機故障時該機上待執行任務的準時觸發
Misfire 策略
tjobs 平臺會有兜底的 misfire 策略以防止任務不能被準時調度時兜底調度過期任務,以保障所有任務觸達率不低于 99.99%,目前提供兩類 Misfire 策略:
1. 馬上觸發一次,已過期任務馬上觸發一次業務回呼(默認用于 singleTask)
2. 盡快觸發一次,忽略已過期任務觸發回呼,本周期內盡快執行一次業務回呼(默認用于 cronTask 和 intervalTask)
部署落地
部署架構
非容災模式線上運行快照(如上圖所示),針對常見的單機宕機或者重啟在 HA 章節已經介紹過,比如 svr-2 宕機或重啟時 g_3 這個跑批任務組會自動 failover 到 svr-12 或者 svr-3 上繼續斷點執行,從而保障高可用性,
針對常見的單機房故障,在任務 dispatch 環節會將一個任務 jobGroup 的主備執行機器分配到不同的 set,從而保障單機房故障時從應用到 DB 都能自動 failover 到其他可用區機房;針對日常的停機發布,由于應用支持分 set 主備 failover,因此發布時按 a、b set 依次發布即可,
性能壓測
詳細的壓測執行程序不在展開,這里只同步一下壓測結論
壓測摸高峰值:任務注冊 1.5w/s、任務觸發 2.2w/s
應用&DB 峰值:
| 機型配置 | 機器數量 | 峰值負載 | 說明 | |
|---|---|---|---|---|
| 應用服務器 | 4C8G | 20 | 45% | 支持橫向擴展,通過擴容保留 20 倍容量空間 |
| 資料庫服務 | 8C32G | 8 | 75% | 目前合并部署,通過調整部署保留 4 倍空間通過 DB 升配保留 8 倍的容量空間 |
峰值 SLA:可用性>99.99%、1s 內觸發占比>99.95%、任務觸達率~100%,
總結
tjobs 作為一個高性能、低延遲的分布式任務調度平臺,在滿足通用的任務注冊、查詢、觸發等基本功能同時,也通過可伸縮的架構、HA 能力、體系化可用性建設保障系統在百億任務量、百萬 TPM 觸發能力下滿足系統可用性、延遲、觸達率 SLA,
支持將任務劃分到不同的分片分配到不同的應用機器上執行,既保留了高峰時百萬 TPM 的觸發能力、也支持低峰時合并部署以節省成本;通過任務前置使用定時任務掃描、記憶體時間輪保證任務及時觸發,保證了任務執行的低延遲;通過主備熱活、自動 failover 能力建設保證系統整體從存盤層到應用的全堆疊高可用,
附錄
層級時間輪的 Golang 實作 | RussellLuo
etcd 選主實作故障主備秒級切換高可用架構 | KL 博客
概覽 :: ElasticJob
作者:timgc
本文來自博客園,作者:古道輕風,轉載請注明原文鏈接:https://www.cnblogs.com/88223100/p/How-to-Design-a-Massive-Task-Scheduling-System.html
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/555623.html
標籤:架構設計
上一篇:微服務中「組件」集成
下一篇:返回列表
