天天看點

如何設計一個海量任務排程系統

作者:閃念基因

作者:timgc

背景

在日常開發中會經常遇到一些需要異步定時執行的業務訴求,典型的使用場景如:逾時未支付訂單關單、每隔 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:

  1. 注冊\觸發可用性>99.95%
  2. 任務觸達率>99.99%
  3. 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 個步驟:

  1. 基于定時排程平台,每個 5min 做一次 cron 排程通知一台 tjobs 機器
  2. tjobs 通過名字服務查詢目前服務下所有可用機器供後續分包、排程
  3. tjobs 根據目前可排程的機器數(n)将 512 個 job 打包成 n 個 jobGroup
  4. 将每個 jobGroup 綁定到一個機器上,通過指定 ip 方式通知服務執行階段二(階段二的詳細實作見下節)

如領域模型中描述 tjobs 跑批任務采用 CyclicBarrier 栅格模式運作,這樣做的目的 1)、期望每個周期各個 job 都能完成所有待觸發任務(即 T1 周期完成 T1 時間之前所有的任務)防止任務積壓;2)、每個任務都以相同的執行周期和參數運作可以幂等,防止任務被重複排程,從平台側盡力提供 only once 的觸發保障。

線程模型

本節接上節會詳細介紹一下階段二每個協程内單個 job 的詳細執行流程,如下圖所示會拆解 5 個步驟:

如何設計一個海量任務排程系統
  1. 掃描本周期内所有待執行的任務,task 在注冊、執行後都會更新下次待執行時間
  2. 将掃描出來的任務按照待觸發時間注冊到 timingwheel 中(timingwheel 為秒級)
  3. timingwheel 到指定時間觸發業務主要完成兩個操作:生成排程流水并更新 task 下次執行狀态 + 執行業務回調
  4. 根據業務回調配置(包括協定類型、回調方式、逾時時間、重試次數等),執行業務回調通知
  5. 更新排程流水狀态,排程成功後或達到重試次數後推進流水到終态

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 應用兩個強依賴服務做容災能力保障。

定時排程平台不可用或排程延遲直接導緻任務不能被準時排程,這裡應對思路有:

  1. 依賴 linux 的 corntab 觸發,存在應用單點問題,導緻整體可用性無法保障
  2. 基于排程平台分鐘級 RTO,通過增大排程周期減少對排程平台依賴度

為達成 p99 延遲<1s,tjobs 會提前将待觸發任務緩存到應用記憶體中,這樣如果 tjobs 應用伺服器當機則該伺服器上本周期内任務都不能被正常排程,隻能等下個執行周期被重新撈起排程,導緻 p99(任務延遲)<1s 不達标,這裡應對思路有:

  1. 縮短排程周期(5'->30''),最多影響單機上 30‘’任務的排程延遲,降低延遲機率但不能徹底解決問題,且縮短周期會和排程平台互動更強(有悖減少排程平台依賴)
  2. 伺服器支援主備 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 峰值:

機型配置機器數量峰值負載說明應用伺服器4C8G2045%支援橫向擴充,通過擴容保留 20 倍容量空間資料庫服務8C32G875%目前合并部署,通過調整部署保留 4 倍空間通過 DB 升配保留 8 倍的容量空間

峰值 SLA:可用性>99.99%、1s 内觸發占比>99.95%、任務觸達率~100%。

總結

tjobs 作為一個高性能、低延遲的分布式任務排程平台,在滿足通用的任務注冊、查詢、觸發等基本功能同時,也通過可伸縮的架構、HA 能力、體系化可用性建設保障系統在百億任務量、百萬 TPM 觸發能力下滿足系統可用性、延遲、觸達率 SLA。

支援将任務劃分到不同的分片配置設定到不同的應用機器上執行,既保留了高峰時百萬 TPM 的觸發能力、也支援低峰時合并部署以節省成本;通過任務前置使用定時任務掃描、記憶體時間輪保證任務及時觸發,保證了任務執行的低延遲;通過主備熱活、自動 failover 能力建設保證系統整體從存儲層到應用的全棧高可用。

附錄

層級時間輪的 Golang 實作 | RussellLuo

etcd 選主實作故障主備秒級切換高可用架構 | KL 部落格

概覽 :: ElasticJob

作者:timgc

來源:微信公衆号:騰訊技術工程

出處:https://mp.weixin.qq.com/s/hv3tTOAdD-SiCq2owCdxZQ

繼續閱讀