Native Flink on Kubernetes 在小紅書的實踐

ApacheFlink 發表於 2022-05-16
Kubernetes Flink

摘要:本文整理自小紅書資料流團隊資深研發工程師何軍在 Flink Forward Asia 2021 平臺建設專場的演講,介紹了小紅書基於 K8s 管理 Flink 任務的建設過程,以及往 Native Flink on K8s 方案遷移過程的一些實踐經驗。主要內容包括:

  1. 多雲部署架構
  2. 業務場景
  3. Helm 叢集管理模式
  4. Native Flink on Kubernetes
  5. 流批一體作業管控平臺
  6. 未來展望

點選檢視直播回放 & 演講PDF

一、多雲部署架構

img

上圖是當前 Flink 叢集多雲部署模式圖。業務資料分散在各個雲廠商之上,為了適配業務資料處理,Flink 叢集自然也進行了多雲部署。這些雲端儲存產品一方面用於內部的離線資料儲存,另外一方面會用於 Flink 做 checkpoint 儲存使用。

在這些雲基礎設施之上,我們搭建了 Flink 引擎支援 SQL 及 JAR 任務的執行,得益於之前做的一項推動任務 SQL 化的工作,當前內部 SQL 任務和 JAR 任務比例已經達到了 9:1。

在此之上是流批一體作業管控平臺,它主要有以下幾個功能:作業開發運維、任務監控報警、任務版本管理、資料血緣分析、後設資料管理、資源管理等。

平臺資料輸入主要有以下三個部分,第一部分是業務資料,存在於業務內部的 DB 系統裡比如 MySQL 或者 MongoDB,還有一部分是前後端打點資料,前端打點主要是使用者在小紅書 APP 端的行為日誌,後端打點主要是 APP 內部應用程式效能指標相關的資料。這些資料經過 Flink 叢集處理之後,會輸出到三個主要業務場景中,首先是訊息匯流排,比如 Kafka 叢集以及 RocketMQ 叢集,其次會輸出到 olap 引擎中,比如 StarRocks 或 Clickhouse,最後會輸出到線上系統,比如 Redkv 或者 ES 供一些線上查詢使用。

二、業務場景

Flink 在小紅書內部的應用場景有很多,比如實時反欺詐監控、實時數倉、實時演算法推薦、實時資料傳輸。本章會著重介紹一下其中兩個場景。

img

第一個是實時推薦演算法訓練。上圖是推薦演算法訓練的執行流程。

Flink 叢集先接收打點服務採集過來的原始資料,對這一部分資料進行歸因並將它寫入到 Kafka 叢集,之後會再有一個 Flink 任務對這部分資料再做一次彙總,然後得到一個 Summary 的標籤資料,針對這個標籤資料,後面還有三條實時處理路徑:

  • 第一,Summary 標籤資料會和推薦引擎推薦出來筆記的特徵資料進行關聯,這個關聯也是在 Flink 任務中進行的,內部稱其為 FeatureJoiner 任務。接著會產出一個演算法訓練的樣本,這個樣本經過演算法訓練之後產出一個推薦模型,而這個模型最終會反饋到實時推薦引擎中。
  • 第二,Summary 標籤資料會通過 Flink 實時寫到 OLAP 引擎中,比如寫到 Hologres 或 Clickhouse 中。
  • 最後, Summary 標籤資料會通過 Flink 寫入到離線 Hive 表中,提供給後續離線報表使用。

img

第二個場景是實時數倉。業務資料包括前後端打點的資料,按照業務分流規則進行處理之後會寫入到 Kafka 或者 RocketMQ 中,後續 Flink 會對這部分資料做實時 ETL 業務處理,最終進入實時資料中心。目前實時資料中心主要是基於 StarRocks 實現的,StarRocks 是一個效能十分強大的 OLAP 引擎,它承載了公司很多實時相關業務。在資料中心之上,我們還支撐了很多重要實時指標,比如實時 DAU、實時 GMV、實時直播歸因、實時廣告計費等。

三、Helm 叢集管理模式

在正式遷入到 Native Flink on K8s 之前很長一段時間內,都是基於 Helm 來進行叢集管理的。Helm 是一個 K8s 上的包管理器,它可以定義、安裝和升級 K8s 應用和服務,同時具有以下幾個特點:

img

  • 第一,可以管理比較複雜的 K8s 應用,建立 Flink 叢集時會建立很多 K8s 相關的資源,例如 service 或者 config map 以及 Deployment 等, Helm 可以將這些資源統一打包成一個 Helm chart,然後進行統一管理,從而不需要感知每一種資源對應的底層描述檔案。
  • 第二,比較方便升級和回滾,只需要執行一條簡單命令就可以進行升級或者回滾。同時因為它的程式碼是和 Flink Client 的程式碼做了隔離,因此在升級過程中不需要去修改 Flink Client 的程式碼,實現了程式碼解耦。
  • 第三,非常易於共享,將 Helm chart 部署在公司私有伺服器上之後,已經可以同時支援多個雲產品的 Flink 叢集管理。

img

上圖是基於 Helm 管理的 Flink 任務生命週期,主要分為啟動任務和停止任務兩個階段。這裡有三個角色,第一個是 Client,它可以是一個 API 請求,也可以是使用者在介面上的一次點選行為。啟動任務時,百川平臺接收到 API 請求後,會通過 Helm Client 命令去執行 install 指令,建立對應的叢集資源,同時內部整合的 Flink Client 也會去檢查當前叢集的 JobManager 是否啟動,如果已經啟動就進行 job 提交。job 提交到叢集執行起來之後,Flink Client 也會不斷地檢查當前 job 的執行狀態,這也是 Helm 管理模式下作業狀態的維護機制。

第二個階段是任務停止階段,Client 會向百川平臺發起一個 stop 命令,接收到 stop 命令之後百川平臺會通過 Flink Client 向 JobManager 發起 cancel 指令,同時檢查這個 cancel 指令有沒有執行成功,發現 job 被 cancel 之後,會通過 Helm Client 去執行 delete 指令,完成叢集資源的銷燬。

img

上圖展示了通過 Helm 建立了哪些 K8s 資源。

  • 首先是最基礎的 JobManager 和 TaskManager Deployment;
  • 第二部分是 ConfigMap,主要是針對 log4j 的配置和各大雲廠商提供的雲端儲存產品相關的配置;
  • 第三部分是 Ingress,目前主要用於 Flink web UI 使用以及訪問 JobManager 當前任務狀態;
  • 第四部分是 Nodeport Service,每啟動一個 JobManager,就會在 JM 上啟動一個 Nodeport Service,並與 Ingress 做繫結;
  • 第五部分是指磁碟資源,主要有以下兩個應用場景:使用 RocksDB Backend 的時候需要去掛載高效雲盤、批處理任務需要掛載磁碟做中間資料交換;
  • 最後一部分是 ServiceMesh,TaskManager 內部會通過 sidecar 形式去訪問第三方服務,比如說 Redkv service,這些 service 的配置也是在這裡面建立的。

img

上圖可以看到 Helm Client 裡面是整合了各大雲廠商提供了 K8s 相關的配置,當它接收到建立任務的引數時,會根據這些引數去渲染出不同的 Helm 模板,並提交到不同的雲上執行,建立出對應的叢集資源。

img

目前的叢集管理模式下,在實際生產過程中還是遇到了不少問題:

  • 第一是 K8s 資源瓶頸問題。因為每啟動一個 JobManager 就會建立一個 NodePort Service,而這個 Service 會在整個叢集範圍內佔用一個埠和一個 ClusterIP。當作業規模達到一定程度的時候,這些埠資源以及 IP 資源就會遇到效能瓶頸了。
  • 第二個是 ServiceMesh 配置成本過高。上文提到 TaskManager 內部會訪問第三方服務,比如說 redkv service,那麼每增加一個 redkv service,就需要去修改對應的配置並完成發版,過程的成本是比較高的。
  • 第三個是存在一定的資源洩露問題。所有的資源建立以及銷燬都是通過執行 Helm 命令來完成的,在某些異常情況下,job 失敗會導致 Helm delete 命令沒有被執行,這個時候就有可能會存在資源洩露的問題。
  • 第四個是映象版本比較難以收斂。在日常的生產過程中,某些線上任務出現了問題,會臨時出一個 hotfix 版本映象並上線執行,久而久之線上就會存在很多版本映象在執行,這對於後面的運維工作以及問題排查產生了非常大的挑戰。
  • 最後一個問題是 UDF 管理複雜度比較高,這是任何分散式計算平臺都會遇到的一個問題。

針對上述這些問題,我們在 Native Flink on K8s 模式下一一進行了優化解決。

四、Native Flink on Kubernetes

首先,為什麼會選擇這種部署模式?因為它具有以下三個特徵:

img

  • 更短的 Failover 時間;
  • 可以實現資源託管,不需要手動建立 TaskManager 的 pod,也可以自動完成銷燬;
  • 具有更加便捷的 HA。在 Flink 1.12 之前,實現 JobManager HA 還是依賴於第三方的 zookeeper。但在 Native Flink on K8s 模式下,可以依賴於原生 K8s 的 leader 選舉機制來完成 JobManager 的 HA。

img

上圖是 Native Flink on K8s 的體系架構圖。Flink Client 裡面整合了一個 K8s Client,它可以直接和 K8s API server 進行通訊,完成 JobManager Deployment 以及 ConfigMap 的建立。JobManager development 建立完成之後,它裡面的 resource manager 模組可以直接和 K8s API server 進行通訊,完成 TaskManager pod 的建立和銷燬工作,這也是它與傳統 session Cluster 模式比較大的不同之處。

img

內部將 UDF 分為兩類:

  • 第一類是平臺內建的,將平時的生產工作中經常使用到的 UDF 進行抽象歸納總結,並內建到映象裡面。映象裡有關於 UDF 的配置檔案,其中有 UDF 的名稱以及型別,同時指定了它對應的實現類。
  • 另外一類是 User-defined UDF,在 Helm 管理模式下,針對使用者自定義的 UDF 管理是比較粗放的,將使用者 project 下所有 UDF 相關的 JAR 包統一載入到 classloader 下,這會導致類衝突問題。而在 Native Flink 模式下,實現了一個 create function using JAR 的語法,可以按需載入使用者所需要的 UDF 對應的 JAR 包,可以極大地緩解類衝突的問題。

img

在原有的模式上,映象管理是通過將所有程式碼統一打包到一個大的 image 裡,但這樣會存在一個問題,對任何模組的修改都需要對整個程式碼庫進行一次編譯打包,而這個過程是非常耗時的。

在 Native Flink 版本下,針對映象版本管理做了一些優化,主要是將 Flink 的 image 拆分為了三個部分,分為 Flink engine、connector 以及第三方外掛。這三個部分都有各自版本號,並且可以自由進行拼裝組合。這項優化降低了引擎打包的頻率,也意味著可以提升發版效率。

拆分之後,Flink 如何將這些映象組合成一個可以執行的映象呢?下面以載入一個 Kafka SDK 外掛為例來進行闡述。job 執行時會從一個動態配置倉庫中獲取當前這個 job 應該使用的 Kafka SDK 版本,並將其傳遞給百川的後端,這個 SDK 版本對應了 docker 倉庫裡面的一個映象,映象只包含一個 SDK 對應的 JAR 包,百川的後端在渲染 pod 模板的時候,會在 InitContainer 階段將 image 載入進來,同時將它 Kafka 的 JAR 包移動到 Flink container 某個指定的目錄下去,以此完成載入。

img

在新的模式下,對 job 狀態維護機制做了一次重構,引入了一個 headless 型別的 service 以及一個 status DB。在 JobManager 模組,通過 JobManager status listener 不斷監聽 job 狀態變化,並將這個變化上傳到 job ststusDB 中,百川平臺可以通過 Query DB 來獲取任務的狀態。另外在某些場景下,可能因為 job 狀態上傳失敗導致百川無法獲取到任務的狀態,百川還是可以走原來的路徑,通過 Ingress 去訪問 JobManager 來獲取任務的狀態。此時的 Ingress 和之前不同之處在於它繫結的是一個 headless service,不需要佔用叢集的 Cluster IP,這就解決了之前模式下 K8s ClusterIP 以及 nodePort 不足的問題。

img

完成上述優化工作以後,面臨的最大的問題就是如何將老版本的任務平滑地遷移到新版本 Flink 1.13 上,這其實是一項非常具有挑戰性的工作。主要做了以下 4 個方面的工作:

  • 第一,相容轉化工具。這個工具會對 SQL 進行轉化,保證 SQL 在 1.13 執行的語法校驗不會出錯。1.10 到 1.13 經歷過幾個大版本的變更, SQL 的定義在眾多方面已經不相容,比如在 1.10 和 1.11 的時候,Kafka connector 的取值是 0.11,到 1.13 之後,對應取值已經變成 universal,如果不做任何轉化,原始 SQL 肯定在 1.13 上沒有辦法執行。
  • 第二,相容檢測工具。這個工具的目的是為了檢查 SQL 執行在 1.13 的時候能不能從一個低版本的 savepoint 去進行恢復。主要從以下幾個方面去做了檢查:operator ID 升級之後,名稱有沒有發生變化;新舊兩個版本對應的 max parallelism 有沒有發生變化,因為 max parallelism 發生變化的時候,在某部分場景下是沒有辦法從一個老的 savepoint 來恢復的。
  • 第三,預編譯。在 1.13 上對轉換之後的 SQL 進行預編譯,看編譯的結果是否能夠正常通過。在相容檢測工具的過程中,也發現了很多從低版本到高版本不相容的地方,引入了新的資料型別機制,1.11 沒有使用 ExternalSerializer,而 1.12 及以後使用 ExternalSerializer 進行包裝;BaseRowSerializer 已經在 Flink 1.11 時候改名成了 RowDataSerializer;資料型別裡面有一個 seriaVersionUID,之前它是一個隨機的 long 型別的數字,而在 1.13 統一固定成了 1。上述種種不相容會導致 1.13 沒有辦法直接從一個低版本的 savepoint 來恢復的。因此針對這些問題,在引擎側做了一些改造。
  • 第四,遷移工具。這個工具的目標主要有以下三點:

    • 首先,對使用者作業的影響時間儘可能降到最低,為了達成這個目標,我們對 Native Flink on K8s 的 application mode 做了比較大的改造。原生的 application mode 是一邊排程一邊申請資源,為了在升級過程中降低對使用者作業的影響,實現了 application mode 下可以提前申請好資源並完成 SQL 的編譯 (即 JobManager 的預啟動),這個過程完成之後,將舊的 job 停掉然後啟動新的 job,整個過程對使用者作業的影響能夠控制在 30 秒以內 (中等規模任務)。
    • 其次,在遷移的過程中要保證狀態不丟失,因為所有遷移都是基於 savepoint 來啟動的,所以這塊的資料是不會有任何丟失的。
    • 最後,如果在升級過程中發生了異常,可以支援異常情況下自動完成回滾。

img

在實際 Application mode 應用過程中,也發現了原生 Flink 的一些問題,並做了對應的處理方案。

例如 JobManager 在 failover 的時候會重新拉起一批新的 TM,會導致 TaskManager 的資源翻倍。如果資源池的資源不足以滿足 double 的需求,就有可能導致 failover 失敗。此外,即使這一次 failover 成功了,但是新啟動的 job 會基於首次啟動時指定的 recover path 來進行恢復,這個時候的位點可能已經是一個十天以前的位點了,這會導致資料重複消費的問題。針對這個問題,在檢測到 JobManager 發生 failover 的時候就會在引擎側直接將 job fail 掉並告警,然後通過人工手動介入來處理。

五、流批一體作業管控平臺

img

流批一體作業管控平臺主要提供了以下幾個模組的功能:作業開發及運維、版本管理、監控報警、資源管理、資料血緣、後設資料管理以及 SDK。其中資源管理主要分為資源隔離和資源推薦,資料血緣主要用於展示 Flink 任務上下游之間的關係,後設資料管理主要是針對使用者 catalog 表。

img

上圖上半部分是 SQL 開發介面,頁面的主體部分 SQL 編輯器,右側有任務的基本資訊、版本資訊、作業引數以及一些資源配置相關的介面元素。

下半部分是任務運維介面,上面提供了很多常規操作,比如停止任務,或先打 savepoint 再停止任務等。

img

作業版本管理分為 Flink SQL 任務以及 Flink JAR 任務。在 SQL 任務介面上可以看到 SQL 經歷過很多次發版,“更多” 按鈕提供了回滾操作。針對 Flink JAR 任務,目前有兩種提交 JAR 任務的方法,可以直接將使用者的 JAR 包上傳到一個分散式儲存路徑,也可以通過指定程式碼倉庫 tag 來指定 JAR 包的版本。

img

資源管理主要分為資源隔離和資源推薦。這裡引入了資源池的概念,並基於以下幾個維度做了切分:

  • 第一個因素是它執行所屬的雲環境;
  • 第二個因素是業務型別;
  • 第三個因素是資源池提供給流還是批任務使用。

另外,針對已經執行一段時間的任務,會結合它歷史執行期間的 CPU、記憶體、延遲 lag 等指標資訊,給出當前任務所需要的最佳 K8s 資源配置推薦結果。

img

Rugal 排程平臺是公司內部一個對標 airflow 的產品,它可以通過百川提供的 SDK 定時建立任務提交到百川平臺。上圖左側是一個 SQL 編輯模板,其中的很多引數資訊都是通過變數的形式來展示。呼叫 SDK 的時候,可以將這些變數對應的實際值傳入進來,並用這些值渲染出具體要執行的 SQL,從而生成具體的執行例項。

六、未來展望

img

最後是對未來工作的規劃。

  • 第一,動態資源調整。目前, Flink job 一旦提交執行,就無法在執行期間修改某個 operator 佔用的資源。所以希望未來能夠在 job 不進行 restart 的情況下,調整某個運算元所佔用的資源。
  • 第二,跨雲多活方案。目前公司核心 P0 作業基本都是雙鏈路的,但都僅限於在單朵雲上。希望針對這些核心任務,實現跨雲雙活方案,其中一個雲上任務出現問題的時候,能夠穩定切換到另外一朵雲上。
  • 第三,批任務資源排程優化。因為批任務大多是在凌晨以後開始執行,同時會排程很多工,有的任務可能因為搶佔不到資源導致無法及時執行,在任務排程執行策略上仍有可以優化的空間。

Flink CDC Meetup · Online

img

時間:5 月 21 日 9:00-12:25

PC 端直播觀看:https://developer.aliyun.com/...

移動端建議關注 ApacheFlink 視訊號預約觀看