作業幫實時計算平臺高可用實踐

作者 | 劉澤強 作業幫高級數據研發工程師

策劃 | Tina

摘 要

隨着業務的高速發展和實時計算的迭代,業務對實時計算的需求越來越多,對實時任務的穩定性要求也越來越高。對實時計算平臺而言,底層調度系統及計算引擎的穩定性、高可用性就變的十分重要。本文主要圍繞作業幫實時計算平臺底層調度系統,從背景現狀、目標與挑戰、方案設計以及未來規劃等幾方面來展開。

背景現狀

開始之前,先簡單瞭解一下之前實時計算平臺後臺調度的架構,如圖 1 所示:

圖 1

實時調度系統採用的是分佈式、去中心無主架構,技術上,使用 AKKA 作爲基本框架,實現高性能、純異步的任務管理。功能上,我們將服務分爲了不同的 group,一個 group 包含多個任務管理節點,一個節點可以同時隸屬於多個 group。在作業幫內部,一個 group 可以理解爲一個集羣環境。爲了達到分佈式負載均衡的目的,每個 node 會負責對應 group 的一部分任務,對任務進行起停、狀態同步。不同 group 的 node 之間,會根據收到的請求的不同,進行請求的轉發與可用性監控。相同 group 的 node 之前,主要涉及到請求的轉發與任務的負載均衡。在外部依賴方面,主要依賴 MySQL、Zookeeper、權限中心和 EMR。其中:

MySQL: 主要負責存儲任務相關的元信息,比如作業配置、執行歷史等

Zookeeper: 主要負責服務的註冊與監聽。新節點啓動的時候,會註冊對應的臨時節點,並通知給集羣裡其他節點;節點下線或者丟失的時候,也會通知集羣其他節點。

權限中心:大數據統一的權限校驗服務,主要用於校驗用戶針對任務的權限。

EMR:我們使用半托管的雲 EMR 產品,使用 Yarn 作爲底層計算引擎,HDFS 作爲 Flink 任務的 state 存儲。

從目前的平臺架構來看,平臺的穩定性在如下三個方面還有一些欠缺和不足:

1. 調度服務本身:

(1) 調度服務內部雖然本身是分佈式的,但是根據任務所提交的 EMR 集羣,進行了分組,比如騰訊雲的任務分組,只能提交到騰訊雲 EMR,這樣當單雲 /AZ 故障的時候,調度服務就會故障,無法服務。

(2) 調度服務同雲的 EMR 共用一個調度分組,不同業務之間在集羣故障的時候,會相互影響。

2. EMR:目前 EMR 屬於半托管模式,雖然有云上的支持,但是穩定性最多也只能達到 99.9%

3. 服務依賴:zookeeper 也是使用雲上 EMR 半托管產品,穩定性也只有 99.9%,故障的時候會導致調度服務不可用。

目標與挑戰

隨着越來越多的公司核心業務在使用實時計算平臺運行任務,業務對實時計算平臺提出了更高的要求:

服務可用性要求 99.95%

支持 AZ 即或者 region 級容災

在現有的架構下,顯然無法滿足這樣的要求。

服務穩定性的保障一般情況下,可以分爲三層:

1. 圍繞研發需求、設計、上線、變更管控來降低故障的發生概率

2. 通過故障演練 / 預案建設的維度,思考怎麼縮短故障處理時長

3. 通過可觀測性等手段,提前預防和發現故障

方案設計

整體架構

針對新的穩定性的挑戰和現有架構,我們主要從以下幾個方面進行改造優化:

調度服務:

支持雙雲部署

服務級別或者服務內部限流隊列針對不同業務進行拆分

EMR 集羣支持 AZ/ 雙雲級別的互備,同時能夠快速切換任務,縮短 flink 任務的異常時間

服務依賴等組件多雲:

MySQL 和權限服務目前都已經是雙雲部署,無需調整

Zookeeper 升級爲全託管的、多 AZ 部署產品

加強服務 / 組件監控,提前發現問題

根據上面的優化點,改造後的整體架構如圖 2 所示。

圖 2

主要模塊設計

瞭解目前項目的整體架構後,下面主要從調度服務多雲 / 多可用區支持、EMR 集羣多 AZ/ 多雲互備和其他改造項方面闡述實現細節。

調度服務多可用區 / 多雲

從上面的架構圖,可以看到,調度服務層面,我們給每個任務組打了標籤。其中 active,表示正常情況下該組實例負責所有任務的管理工作;backup 表示該組實例作爲 active 組的備份組,只有在所有 active 組的都掛掉的情況,纔會接管並負責任務的管理工作。之所以這麼設計,是因爲我們目前 80% 的 flink 任務都是使用 per-job 模式運行的,爲了防止跨雲造成提交任務性能損耗。雖然目前的設計是針對多雲部署的,但是同樣支持多 AZ 部署。當多 AZ 部署的時候,可以將任務組的標籤都設置爲 active,這樣,所有的節點都會參數任務的管理工作。

調度服務主備在任務負載均衡和管理的流程如下:

當 Active 任務組節點全部丟失的情況下, Backup 任務組接管全部任務,並內部進行負載均衡和任務管理。

當只有部分 Active 任務組 節點 丟失的情況下,Active 任務組內部剩餘節點進行負載均衡和任務管理。

當 Active 任務組有節點新增的時候, 如果當前有其他存活 Active 任務組節點,Active 任務內部進行負載均衡和任務管理,如果當前沒有其他存活 Active 任務組節點,則從 Backup 任務組接管所有任務,進行管理。

當 Backup 任務組 部分節點丟失情況下,如果當前沒有存活 Active 任務組節點,Backup 任務組內部進行負載均衡和任務管理,否則,不做處理。

當 Backup 任務組新增節點的情況下, 如果當前沒有存活 Active 任務組節點,Backup 任務組內部進行負載均衡和任務管理,否則,不做處理。

目前調服服務的任務負載均衡 / 管理 邏輯,採用 hash(task_name) % num_of_group_nodes 的方式決定任務應該由哪個節點進行負責。

EMR 集羣主多 AZ/ 多雲備切換

想要實現高效快速 EMR 集羣災備需要有幾個問題需要解決:

1. 往什麼地方切。一般情況下,企業的跨雲或者跨城帶寬是有限的。所以 EMR 災備最好是多 AZ 部署。

2. 基於什麼標準切。EMR 集羣故障的情況下,怎麼保證 Flink 任務真正的被殺死了,避免任務雙跑,影響數據的準確性。

3. 如何透明的切作業。因爲 Flink 任務都是長生命週期的,帶着 state 中間計算結果,我們目前的 state 是存儲在 EMR 的 HDFS 上的,切換集羣的話,就需要保證 state 在切換後可用。

Flink 任務存算分離

目前 Flink 任務的 state 使用的是 EMR 的 HDFS 存儲的,是存算一體的,想要滿足 Flink 任務切換集羣后 state 仍然可用,只能存算分離。業界推薦的方案是使用對象存儲來存儲 state。

我們都知道,對象存儲和 HDFS 在性能上面還是有比較大的差異的,在使用對象存儲替代之前,我們需要想看看切換到對象存儲後,checkpoint 的時長業務是否可以接受。

作業幫內部,基本上大家用的都是 FSBackend, 沒有特別大的狀,狀態基本都在 1G 以下。下表爲目前我們內部任務的 state 大小統計情況:

我們重點測試了一下 1M, 64M, 512M, 1G 狀態在使用 HDFS OSS 作爲 FsStateBackend 的性能區別,發現對應的 checkpoint 時間差別不大,都在可接受範圍。

我們重點測試了一下 1M, 64M, 512M, 1G 狀態在使用 HDFS OSS 作爲 FsStateBackend 的性能區別,發現對應的 checkpoint 時間差別不大,都在可接受範圍。

因此將 state 切換到對象存儲,在作業幫內部是完全可行的。

爲了防止業務之間的相互影響,我們針對每個 EMR 集羣,都設置了專屬的存儲桶,針對自身 EMR 可讀寫,針對其他 EMR 只可讀。出於性能和成本的考慮,針對不是穩定性要求不是很高的業務,我們仍然將 state 存儲在 HDFS 上。

EMR 集羣容災切換

首先,往什麼地方切?我們目前選擇的是 EMR 多 AZ 互備,防止跨雲數據傳輸導致專線打滿。

其次,基於什麼標準切?在決定切換標準前,我們需要知道,EMR 的故障都有哪些場景?在什麼場景下,我們可以確認 Flink 任務能否被殺死,確保任務不會雙跑。

EMR 故障的場景,大體可以分爲兩大類:

網絡問題:EMR 可能正常,也可能不正常。

EMR 集羣異常:

服務 GC 無響應等問題

兩個 Master 均爲 standby 狀態

兩個 Master 因爲內存等原因頻繁啓停,無法正常工作

目前我們的任務都開啓了 Flink 的高可用,這樣當 JobManager 因爲某種原因掛掉的情況下,任務可以自行恢復。同時,在 EMR 層面,我們設置了 yarn.resourcemanager.recovery.enable=true, 這樣在 ResourceManager 從異常恢復的時候,會自動恢復之前異常的任務。

因此,爲了確保 EMR 集羣故障的情況下,任務能夠被殺死,我們需要達成兩個條件之一:

1. 調度服務可以明確知道,任務被殺死了

2. ResourceManager 異常恢復的時候,不要恢復應該被殺死的任務

針對條件一,我們可以通過 Yarn Java SDK API 進行殺死任務和通過 Flink Rest API 殺死任務。

YarnClient.kill(app_id)

curl -XPATCH http://:

/jobs/?mode=cancel, 其中 jm_addr 可以通過 AppReport 的 originalTrackingUrl 獲取。

針對條件二,我們可以通過設置一些參數,保證 ResourceManager 異常的情況下,不會恢復任務。相關參數參考下表:

之前我們任務的殺死邏輯很簡單,收到 kill 命令以後,會不斷循環的通過 YarnClient.kill(app_id) 的方式,殺死任務。爲了應對 EMR 異常的場景:

我們首先添加了 EMR 異常的檢測邏輯,使用一個專有的 actor 定期檢測 Yarn 狀態,將 EMR 集羣的狀態分爲了四種狀態:

Normal: 狀態正常

VoteAbNormal: 投票異常狀態

ThresholdAbnormal:超過閾值異常狀態

MaunalAbnormal:任務標記異常狀態

具體檢測邏輯如下:

考慮到目前平臺沒有任務優先級的概念,因此,目前 EMR 集羣異常切換,是需要用戶手動發起的,通過平臺選擇高優的任務,批量先殺死故障 EMR 集羣的任務,然後更新任務並遷移的備用的 EMR 集羣上。

調度服務內部的殺死任務流程如下圖所示:

其他功能項

爲了避免業務之間的相互影響,保證異常切換任務提交速度。我們針對任務提交組做了如下改造:

針對穩定性要求高的業務,我們準備了專用的任務提交組節點。

針對穩定性要求不是很高的業務,仍然共用任務提交組,只是基於 EMR Yarn 隊列,針對不同業務方的任務,做了提交限流隊列的分組。

未來規劃

未來我們實時計算調度平臺在穩定性方面的一些規劃:

計算引擎遷移到雲 K8S 上,降低運維成本,同時提升引擎的 SLA。

底層實時調度服務容器化,提升穩定性、快速擴縮容。

調度服務依賴如 Zookeeper 等多雲部署,兼容雲間斷網等

https://hadoop.apache.org/docs/r2.8.5/hadoop-yarn/hadoop-yarn-common/yarn-default.xml