SparkSQL on K8s 在網易傳媒的落地實踐

隨着雲原生技術的發展和成熟,大數據基礎設施積極擁抱雲原生是業內發展的一大趨勢。網易傳媒在 2021 年成功將 SparkSQL 部署到了 K8s 集羣,並實現與部分在線業務的混合部署,到目前已經穩定運行了一年多。期間傳媒聯合杭研 Spark 內核團隊和雲計算團隊對出現的問題進行了持續的改進,本文將對這些落地優化實踐進行初步的梳理總結,希望能給大家帶來一些有用的參考。目前,傳媒大數據中心的大部分 SparkSQL 任務都已經遷移到了 K8s 集羣,但仍有一部分算力保留在 Yarn 集羣,作業調度主要依託於有數平臺,SparkSQL 任務的提交方式以 Kyuubi [1] 爲主,Spark 版本主要基於 3.1.2 進行演進,下圖簡單描述了我們當前 Spark 離線計算的基本架構:

以下將分別從 on K8s 落地收益、任務遷移方案、集羣和任務運行監控、任務資源佔用治理、任務調度優化等幾個方面逐漸展開介紹。

SparkSQL 遷移到 K8s 的收益

傳媒大數據將 SparkSQL 遷移到 K8s 主要基於如下考量:

  • 可以將計算和存儲進行解耦,即存算分離。在存儲和計算耦合的架構中,由於各業務場景對存儲和計算的需求不平衡,綁定兩者同步進行伸縮,會出現其中一種資源浪費的情況;將計算和存儲解耦後則可以根據需要分別進行彈性伸縮,系統在負載均衡調度方面可以更加靈活。
  • 統一算力資源池實現統籌調度,SparkSQL 可以作爲離線業務與其它在線業務進行混混部達到峯谷互補的效果,有助於提升服務器資源利用率和管理運維效率,節約總成本。

任務由 Yarn 遷移到 K8S 的方案

遷移方面,我們已提前將大部分任務由HiveSQL 遷移到了 SparkSQL 引擎[2] ,而 SparkSQL 從 Yarn 集羣上切換到 K8S 集羣上對用戶來說基本上是無感知的。不過,遷移初期爲了保證未知風險儘量可控,我們採取瞭如下措施:

  • 先遷移非核心的下游任務來踩坑,逐步擴大規模再推進到上游任務。
  • 先遷移自定義腳本類型任務,得益於對 Kyuubi 的使用,只需要少量代碼就可以方便地將失敗任務調度至 Yarn 集羣進行重試。

這樣我們在遷移初期儘量減少了對需要保障的核心 SLA 任務鏈路的影響。

任務在 K8s 上的運行監控

任務遷移到 K8s 之後,在遇到問題進行排查時,用戶都迫切希望能儘快看到作業的運行情況從而快速進行問題診斷和作業優化。傳統的 SparkSQL on Yarn 場景,我們有 Yarn 的 web 頁面作爲入口來查看隊列資源佔用和任務運行狀態,但在 K8s 環境下並沒有一個類似的統一入口。而 Spark History Server 上的任務統計列表因爲需要等待任務運行日誌上傳至 HDFS 後才能解析展示,相對要滯後許多,這導致了在遷移初期我們對集羣和任務的運行情況基本處於兩眼一抹黑的狀態。爲了解決這個問題,我們首先設置所有任務均使用 client 模式提交 SparkSQL,讓 Spark Driver 在調度機本地運行,這樣一來,便只需要在幾臺任務調度機上部署監控程序,通過 Spark Driver 本地的 Spark http 接口獲取當前任務運行信息即可。這種方式雖然不是很符合所有組件都跑在容器中的雲原生理念,但是人力成本相對較低,在前期簡化了我們的監控告警工作,將來待方案進一步完善後再切換到 cluster 運行模式。另外,隨着優化工作的推進,我們跟杭研 Spark 內核組配合搭建了 Hygieia 任務運行指標監控服務,跟部門運維和杭研雲計算團隊增加了調度資源相關的監控,對監控需求進行了進一步完善。以下簡單列舉了日常看得比較多的幾個監控報表:

  • 任務實時運行列表:實時監控集羣上當前的任務列表,可以直接跳轉相應的 Spark UI 來查看實時運行情況。
  • 任務資源分配監控:監控 CPU、內存等資源分配量的變化,可以按任務或用戶維度進行堆疊,主要用於監控集羣的整體運行情況,比如是否有異常任務或用戶搶佔大量資源、或者資源分配速度不正常等等。
  • 任務運行狀態統計:監控集羣上運行中和 pending 的任務數或 pod 數,可以直觀地反映集羣資源的競爭程度和 K8s 的調度負載。
  • 集羣運行指標監控:主要通過哨兵提供的監控頁面查看,具體各業務的資源容量和實際使用量情況主要通過雲計算團隊提供的 Grafana 監控查看。

部分監控報表實際使用中,通過上述監控,基本能較全面地查看集羣和 SparkSQL 任務的運行狀態,有助於快速定位問題。

任務資源佔用治理

任務在 K8S 上平穩運行之後,大家的關注點逐漸轉移到性能問題上來,尤其是集羣的資源使用率和基線任務的產出時間等,這方面我們針對 K8S 集羣的特點陸續落地了一系列優化策略,以下分別從 CPU、內存、磁盤等資源角度進行介紹。

1. CPU

爲儘可能地提高集羣的承載容量,最大化能併發運行的 executor pod 數量,我們根據集羣和任務的統計情況對 CPU 做了一定比例的超售。經過對比 CPU 申請量和實際使用量的整體比值,最終統一將任務的 spark.kubernetes.executor.request.cores 參數配置爲 1,而 spark.kubernetes.executor.limit.cores 不做限制,同時將 spark.executor.cores 配置爲 4,即 excutor 按 1 核的申請量跑 4 併發的 task,以此最大限度地壓榨集羣的 CPU 算力。CPU 超售會加重集羣負載不均衡的問題,造成部分節點負載過高,但得益於雲計算團隊開發的 Zeus 混部調度器,可以動態更新資源隔離配置,避免對在線業務產生過多幹擾 [3]。

2. 內存

由於之前 Yarn 集羣的內存資源較充足,業務上對各任務內存申請的審批也相對寬鬆,導致任務內存申請方面普遍存在 “虛胖” 的問題,從而導致在遷移到 K8s 集羣后內存成爲瓶頸,集羣併發上不去出現算力浪費情況。與 CPU 相比,內存作爲一種不可壓縮資源相對緊缺,且不同任務所需的內存大小不一,不能對任務內存申請執行一刀切的策略。如何安全且高效地降低 SparkSQL 任務的內存分配,是一個關鍵的問題。爲了給每個任務評估出一個相對合理的內存申請值,我們首先基於前文提到的 Spark 監控插件 Hygieia,實現了對 SparkSQL 任務運行指標的實時採集。在積累了一段時間的樣本數據後,我們開始根據每個任務的歷史統計數據對內存申請進行例行化調整,調整策略主要如下:

  • 取任務近一段時間所有實例 executor 堆內和堆外內存使用量之和的最大值,按 128MB 爲單位向上取整作爲內存申請建議值進行調整。另外,爲了安全考慮設置了調整下限。
  • 對調整後的任務進行一段時間的健康監測,包括任務運行時長和 GC 吞吐量等指標。當任務運行時長出現延長或者 GC 吞吐量下降時,則需要回調內存或人工介入排查。一個簡化版的模型是,當任務內存被削減後一週內的 GC 吞吐量中位位數若低於 95%,每低 1% 則增加 512MB 內存,直至恢復到調整前。

具體執行上,我們通過猛獁調度平臺的開放 API 做到了每天例行自動化調整。另外,爲了避免任務被頻繁調整無法評估效果,還針對每個任務設置了調整冷卻期等。截止到當前,調整策略對業務而言基本上做到了透明無感知和 0 事故。下圖展示了自調整策略執行以來內存分配量 95 分位與 CPU 實際使用量 95 分位比值的變化曲線,當前比值已降至接近 4:1 的合理水平,集羣 0-7 點的 CPU 利用率保持在 80+%,且 GC 吞吐量保持在 95+%:

3. 磁盤

雖然 “內存計算” 是 Spark 的主要特性之一,但在實際場景中往往由於任務 shuffle 數據量大,導致對磁盤容量和 IO 速度要求也較高。目前傳媒使用的還是 ESS 方案,尚未切換到 RSS 方案 [4],且傳媒 K8s 集羣掛載的數據盤容量有限,如果一些異常任務發生傾斜,極易引發個別節點磁盤被寫滿的風險,導致任務失敗和重試。爲了解決這些問題,主要措施如下:

  • 數據盤統一使用 SSD:遷移之初,K8s 集羣使用了部分雲盤,而 Spark 離線計算任務高峯期突發數據流量非常大,導致雲盤讀寫出現明顯延遲,集羣 CPU 的 io_wait 時間佔比很高,任務大量時間耗費在等 IO 上。全部改爲使用 SSD 後,運行速度得到明顯提升。
  • 使用 zstd 壓縮 shuffle 數據:zstd 壓縮算法性能優秀,在壓縮率和耗時上都有卓越的表現。相較於 shuffle 默認的壓縮算法 lz4,zstd level1 壓縮級別在任務性能表現上基本持平,但壓縮率提升了近一倍。經實踐,切換到 zstd 壓縮可顯著降低 shuffle 寫盤的數據量,有效緩解磁盤容量上和 IO 上的壓力,從而提升容錯上限。下圖展示了切換 zstd 壓縮算法前後磁盤空間使用率的變化情況:

  • 大 shuffle 任務調度至 Yarn:由於傳媒當前還保留有一部分 Yarn 算力,將 shuffle 量較大的任務調度至 Yarn 可以有效減輕 K8S shuffle 服務的壓力,做到物盡其用。

任務調度優化傳媒 SparkSQL 任務主要以分鐘級爲主,且基於 3.1.2 版本開啓了動態資源分配機制,運行時會根據需求動態申請和釋放 executor,實際使用中對集羣的調度吞吐能力有一定的性能要求,這本身對以調度長時服務起家的 K8s 提出了不小的挑戰。我們遇到的問題和應對策略主要如下:

1. 調度性能瓶頸問題優化

上文提到 SparkSQL 任務是作爲離線業務與其它在線業務進行混部的,且初期共用一個線性調度器,而由於離線任務的優先級都要低於在線業務,導致出現了高峯期時集羣有資源但是遲遲調度不上去的現象。通過與杭研 Spark 內核團隊配合,我們對每個任務所能併發申請的 pod 數進行了限制,一定程度上減少了 excutor pod 的頻繁申請和釋放。通過與杭研雲計算團隊配合,我們爲 SparkSQL 獨立出了一個專用的調度器,將 SparkSQL 的調度與其它業務的調度負載隔離開,從而消除了 SparkSQL 在調度性能上的瓶頸。

2. 針對調度傾斜開啓反親和調度

如果某個任務的 excutor pod 集中在少數幾個節點上,而這個任務的 shuffle 或計算又比較重,比較容易導致節點磁盤寫滿或 CPU 負載高,實際場景中這種情況並不少見。爲了應對這個問題,我們在 executor pod 的調度上開啓了反親和特性,即同一個任務的 pod 儘可能分散到不同的節點上,也取得了比較不錯的效果。

3. 開啓優先級調度保障核心任務鏈路

隨着遷移到 K8S 集羣上任務規模的擴大,資源競爭逐漸加劇,高峯期頻繁出現任務長時間 pending 的情況,尤其是在出現重大新聞事件時需要把集羣資源傾斜給線上關鍵業務時。這個時候給不同 SLA 級別的任務設置不同資源分配優先級的需求格外強烈起來。通過與杭研 Spark 內核團隊配合,我們爲任務指定了不同的 PriorityClass 來對調度優先級進行區分。下圖分別是關閉和開啓優先級調度時的 SLA 任務運行情況,其中每個顏色代表一個任務所佔用的 pod 數。可以看到,開啓優先級調度時,SLA 任務在高峯期時的資源分配得到了優先保障,pod 數的峯值提高 20+%,任務 “身材” 明顯 “瘦” 下來,拖尾現象得到緩解。

關閉優先級調度時

開啓優先級調度時優先級調度的落地,爲重大新聞事件時的高優數據保障工作提供了一個託底的機制。

總結與展望

傳媒 SparkSQL on K8S 穩定運行一年來,隨着優化方案的落地,集羣的規模相較於初期已經縮容了 30+%,但基線產出仍保持了穩定,甚至有所提升。這與部門內和杭研相關團隊的有力支持是分不開的,在此一併表示感謝!後續工作將圍繞以下幾個方面展開:

1. 繼續擴大 SparkSQL on K8s 的規模

後續傳媒將繼續擴大 SparkSQL on K8s 的規模,統一算力資源池,擴大規模效應。當前大數據技術中心正在協助推薦業務開展遷移驗證,也已經取得了不錯的測試效果。

2. 探索與 Flink on K8s 混部

當前傳媒也正在落地 Flink on K8s 的雲原生方案,並探索與 SparkSQL on K8s 做混部。相較於後臺線上服務,Flink 實時計算的流量特徵更加明顯,負載波動更能與 SparkSQL on K8s 互補,且同屬於大數據業務便於統籌調度資源。

3. 探索存儲的雲原生方案

存算分離後,目前傳媒大數據的存儲資源仍全部集中在 HDFS 集羣上,由於與 Yarn 計算資源的綁定,而導致缺乏相應的彈性伸縮能力。當前對象存儲和基於對象存儲的 Hadoop 兼容文件系統發展迅速,傳媒也正在展開對該模式的探索,以期實現存儲資源池的統一。傳媒當前 SparkSQL on K8s 使用的 ESS shuffle 方案依賴於計算節點上的磁盤來存儲臨時數據,還不算完整雲原生意義上的存算分離,後續將開始調研並落地 RSS 相關的方案。

引用:

  1. Apache Kyuubi (Incubating) 
  2. Hive SQL 遷移 Spark SQL 在網易傳媒的實踐
  3. 降本增效黑科技 | 基於 Kubernetes 的在 / 離線業務混部
  4. Apache Uniffle (Incubating)

作者:魯成祥 易順

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章