深度介紹Flink在位元組跳動資料流的實踐

位元組跳動資料平臺 發表於 2022-01-12
Flink

本文是位元組跳動資料平臺開發套件團隊在1月9日Flink Forward Asia 2021: Flink Forward 峰會上的演講分享,將著重分享Flink在位元組跳動資料流的實踐。

位元組跳動資料流的業務背景

資料流處理的主要是埋點日誌。埋點,也叫Event Tracking,是資料和業務之間的橋樑,是資料分析、推薦、運營的基石。

使用者在使用App、小程式、Web等各種線上應用時產生的行為,主要通過埋點的形式進行採集上報,按不同的來源分為客戶端埋點、Web端埋點、服務端埋點。

不同來源的埋點都通過資料流的日誌採集服務接收到MQ,然後經過一系列的Flink實時ETL對埋點進行資料標準化、資料清洗、實時風控反作弊等處理,最終分發到下游,主要的下游包括ABTest、推薦、行為分析系統、實時數倉、離線數倉。

所以,如果用一句話來概括資料流主要業務,其實就是埋點的收集、清洗、分發。

目前在位元組跳動,清洗和分發環節是基於Flink搭建的。
深度介紹Flink在位元組跳動資料流的實踐

01 - 資料流業務規模

  • 業務數量:在 位元組跳動,包括抖音、今日頭條、西瓜視訊、番茄小說在內的3000多個大大小小的APP和服務都接入了資料流。
  • 資料流峰值流量:當前,位元組跳動埋點資料流峰值流量超過1億每秒,每天處理超過萬億量級埋點,PB級資料儲存增量。
  • ETL任務規模:目前,位元組跳動資料流在多個機房部署超過1000個Flink任務和超過1000個MQ Topic,使用超過50W Core CPU,單任務最大12W Core CPU ,Topic最大10000 Partitio。

02 - 資料流業務挑戰

位元組跳動資料流ETL遇到的挑戰主要有四點:

  • 第一點,流量大,任務規模大。

  • 第二點,處在所有產品資料鏈路最上游,下游業務多,ETL需求變化頻繁。

  • 第三點,高SLA要求,下游推薦、實時數倉等業務對穩定性和時效性有比較高的要求。

  • 最後一點,在流量大、業務多、SLA要求高的情況下,針對流量、成本、SLA保障等多維度的綜合治理也面臨挑戰。

下面從兩個資料流業務場景中介紹一下我們遇到的業務挑戰。

1、UserAction ETL場景

在UserAction ETL場景中,我們遇到的核心需求是:種類繁多且流量巨大的客戶端埋點需求和ETL規則動態更新的需求。

在位元組內部,客戶端的埋點種類繁多且流量巨大,而推薦關注的只是部分埋點,因此為了提升下游推薦系統處理效率,會在資料流配置一些ETL規則,對埋點進行過濾,並對欄位進行刪減、對映、標準化之類的清洗處理,將埋點打上不同的動作型別標識。

處理之後的埋點一般稱之為UserAction,UserAction資料會和服務端展現等資料在推薦Joiner任務的分鐘級視窗中進行拼接Join,產出Instance訓練樣本。
深度介紹Flink在位元組跳動資料流的實踐

舉個例子:一個客戶端的文章點贊埋點描述了使用者在一個時間點對某一篇文章進行了點贊操作,埋點經過資料流日誌採集服務進入資料流ETL鏈路,通過UserAction ETL處理後實時地進入到推薦Joiner任務中拼接生成樣本更新推薦模型,從而提升使用者體驗。

如果產出UserAction資料的ETL鏈路出現比較大的延遲,那麼就不能在視窗內及時完成拼接,可能導致使用者體驗下降。

因此對於推薦來說,資料流的時效性是一個強需求。

而推薦模型的迭代、產品埋點的變動都可能導致UserAction的ETL規則的變動。如果ETL規則硬編碼在程式碼中,每次修改都需要升級程式碼並重啟Flink Job,會影響資料流穩定性和資料的時效性。因此,這個場景的另一個需求就是ETL規則的動態更新。

2、資料分流場景

目前,抖音業務的埋點Topic晚高峰流量超過1億/秒,而下游電商、直播、短視訊等不同業務的實時數倉關注的埋點範圍實際上都只是其中的一小部分。

如果各業務分別使用一個Flink任務,消費抖音埋點Topic,過濾消費各自關注的埋點,需要消耗大量Yarn資源,同時會造成MQ叢集頻寬扇出嚴重,影響MQ叢集的穩定性。

因此,資料流提供了資料分流服務,使用一個Flink任務消費上游埋點Topic,然後通過配置規則的方式,將各業務關注的埋點分流到下游小Topic中,再提供給各個業務消費。這樣就減少了不必要的反序列化開銷,同時降低了MQ叢集頻寬扇出比例。
深度介紹Flink在位元組跳動資料流的實踐

在資料分流場景中,核心需要解決的是高穩定的SLA。因為斷流、資料延遲可能會影響推薦效果、廣告收入、實時資料包表。

同時隨著業務發展,實時資料需求日益增加,分流規則新增和修改也會日益頻繁。如果每次規則變動都需要修改程式碼並重啟Flink Job,會影響很多下游,因此分流規則的動態更新也是這一場景中的強需求。

位元組跳動資料流實踐

01-資料流ETL鏈路建設

位元組跳動資料流ETL鏈路建設主要經歷了三個階段:

深度介紹Flink在位元組跳動資料流的實踐

第一階段是2018年以前業務需求快速迭代的早期階段

主要使用PyJStorm和基於Python的規則引擎構建主要的流式資料處理鏈路。其特點是比較靈活,可以快速支援業務需求。

但隨著埋點流量快速上漲,PyJStorm暴露出很多穩定性和運維上的問題,效能也不足以支撐業務的增長。

2018年,公司內部開始大力推廣Flink,並且針對大量舊任務使用PyJStorm的情況,提供了PyJStorm到PyFlink的相容適配。流式任務託管平臺的建設一定程度上解決了流式任務運維管理的問題。資料流ETL鏈路也在2018年全面遷移到了PyFlink,進入了流式計算的新時代。

第二個階段是2018至2020年

隨著流量的進一步上漲,PyFlink和Kafka的效能瓶頸、以及JSON資料格式帶來的效能和資料質量問題都一一顯現出來,與此同時下游業務對延遲、資料質量的敏感程度卻是與日俱增。

於是,我們一方面對一些痛點進行了針對性的優化。另一方面,花費1年多的時間將整個ETL鏈路從PyFlink切換到了Java Flink,使用基於Groovy的規則引擎替換了基於Python的規則引擎,使用ProtoBuf替換了JSON。

資料流ETL新鏈路,相比舊鏈路效能提升了1倍。

與此同時,一站式大資料開發平臺和流量平臺的建設提升了資料流在任務開發運維、ETL規則管理、埋點後設資料管理、多機房容災降級等多方面的能力。

第三個階段是從2021年開始

在全球資源供應緊張的背景下,進一步提升資料流ETL效能和穩定性,滿足流量增長和需求增長的同時,降低資源成本和運維成本,是這一階段的主要目標。我們主要從三個方面進行了優化:

  • 優化引擎效能。隨著流量和ETL規則的不斷增加,基於Groovy的規則引擎使用的資源也不斷增加,於是我們基於Janino進行了重構,引擎效能得到數倍提升。

  • 優化埋點治理體系。我們基於流量平臺建設了一套比較完善的埋點治理體系,通過無用埋點下線、埋點取樣等手段降低埋點成本。

  • 優化鏈路。我們進行了鏈路分級,不同等級的鏈路保障不同的SLA,在資源不足的情況下優先保障高優埋點鏈路。

從2018年到2020年,我們持續在資料流Flink ETL Job應對需求挑戰上取得了一些實踐效果。下圖展示了資料流Flink ETL Job是如何支援動態更新的,在不重啟任務的情況下,實時更新上下游Schema、規則處理邏輯、修改路由拓撲。
深度介紹Flink在位元組跳動資料流的實踐

流量平臺Config Center為資料流Flink ETL Job提供上下游資料集拓撲關係、Schema、ETL規則和UDF等後設資料。

資料流Flink ETL Job中的每個TaskManager中會有一個Meta Updater更新執行緒,更新執行緒每分鐘通過RPC請求從流量平臺拉取並更新相關後設資料。

Source將從MQ中消費到的資料傳入ProcessFunction,根據MQ對應的Schema反序列化為InputMessage,然後進入規則引擎中,通過規則索引匹配出需要執行的規則,每條規則抽象為一個Filter模組和一個action模組,Filter和action都支援UDF ,Filter篩選命中後,通過action模組對輸入資料進行欄位對映和清洗,然後寫出到OutputMessage中。

每條規則也指定了對應的下游資料集,路由資訊也會一併寫出到OutputMessage。OutputMessage輸出到Sink後,Sink根據OutputMessage中的路由資訊將資料傳送到SinkManager管理的Client,由對應的Client傳送到下游MQ。

這裡解釋一下我們為什麼讓每個TaskManager通過一個MetaData updater定時去更新後設資料,而不是通過增加一條後設資料流來更新。這麼做的原因主要是因為使用後設資料流更新的方式需要開啟Checkpoint以儲存後設資料的狀態,而在位元組跳動資料流這樣的大流量場景下,開啟Checkpoint會導致在Failover時產生大量重複資料,下游無法接受。

1、規則引擎的解決方案

資料流Flink ETL Job使用的規則引擎經歷了從Python到Groovy再到Janino的迭代。規則引擎對於資料流來說最主要的就是提供動態更新ETL規則的能力。

Python由於指令碼語言本身的靈活性,動態載入規則實現起來比較簡單,通過Compile函式可以將一段規則程式碼片段編譯成位元組程式碼,再通過eval函式進行呼叫即可。但存在效能較低,規則缺乏管理的問題。

遷移到Java Flink後,我們在流量平臺上統一管理ETL規則、Schema、資料集等後設資料。使用者在流量平臺編輯ETL規則,規則從前端檢視傳送到後端,經過一系列校驗後儲存為邏輯規則,引擎將邏輯規則編譯為物理規則執行。Groovy本身相容Java,所以我們可以通過GroovyClassLoader動態的載入規則、UDF。

但使用Groovy,雖然效能比Python提高了很多倍,但額外的開銷仍比較大,因此我們又藉助Janino可以高效動態編譯Java類並載入到JVM直接執行的能力,將Groovy替換為Janino。

除了規則引擎的迭代,我們在平臺側的測試、釋出、監控和報警方面也做了很多建設。

測試釋出環節支援了規則的線下測試、線上除錯、灰度釋出等功能,監控環節則是支援欄位、規則、任務等不同粒度的異常監控,並支援了規則流量的波動報警、任務的資源報警等功能。

規則引擎的應用解決了資料流ETL鏈路如何快速響應業務需求的問題,實現了動態調整ETL規則不需要修改程式碼、重啟任務。

但規則引擎本身的迭代、流量增長導致的資源擴容等場景還是需要升級重啟Flink任務,引發斷流。除了重啟斷流外,大任務還可能遇到啟動慢、佇列資源不足或資源碎片導致起不來等問題。

深度介紹Flink在位元組跳動資料流的實踐

2、Flink拆分任務的實踐

針對這些痛點,我們上線了Flink拆分任務。Flink拆分任務本質上就是將一個大任務拆分為一組子任務,每個子任務按比例消費上游Topic一部分Partition,處理後再分別寫出到下游Topic。
深度介紹Flink在位元組跳動資料流的實踐

舉個例子,上游Topic有200個Partition,我們在大資料研發治理套件DataLeap的資料開發上配置一個Flink拆分任務只需要指定每個子任務的流量比例,其餘引數都可以按比例自動同步。

拆分任務的應用使得資料流Flink ETL Job除了規則粒度的灰度釋出能力,還具備了Job粒度的灰度釋出能力,從此升級、擴容不斷流,上線風險更可控。同時,由於拆分任務各子任務是獨立的,因此單個子任務出現反壓、fail-over不會影響其他子任務,對下游的影響更小。另外一個優點是單個子任務資源使用量更小,子任務可以同時在多個佇列靈活部署。

在流量迅速增長的階段,資料流最開始是通過Kafka Connector直接寫Kafka。但是由於資料流Flink ETL Job任務處理的流量大,Sink比較多,批量傳送的效率不高,Kafka叢集寫入請求量很大,另外由於每個Sink一個Client,Client與Kafka叢集間建立的連線數很多,而Kafka叢集由於Controller效能瓶頸也無法繼續擴容。

為了緩解Kafka叢集壓力,資料流Flink ETL Job引入了DataBus元件。

DataBus以Agent的方式部署Yarn節點上,Agent中每個Channel對應一個Kafka Topic。資料流FlinkETL Job每個TM中的SinkManager使用DataBus Client 通過 Unix Domain Socket的方式將資料傳送到DataBus Agent 的Channel中,再由Channel將資料批量傳送到對應的Kafka Topic。

由於每個Yarn節點上所有的TM都先把資料傳送到本機的Databus Agent,每個Databus channel聚合了機器上所有TM Sink寫同一個Topic的資料,因此批量傳送的效率非常高,極大的降低了Kafka叢集的寫入請求量,與Kafka叢集之間需要建立的連線也更少。

同時,單個請求中資料條數的增加帶來更高的壓縮效率,在Databus  Agent 上開啟了ZSTD壓縮後,Kafka叢集寫入頻寬降低了37%,極大的緩解了Kafka叢集的壓力。

深度介紹Flink在位元組跳動資料流的實踐

春晚活動是萬眾矚目的一大盛事,2021年春晚活動期間資料流對相關的埋點鏈路進行了重點保障。

首先是完成了多機房的容災部署並準備了多種切流預案,正常情況下流量會均勻的打到多個機房,MQ多機房同步,Flink ETL Job都從本地消費。如果某個機房出現網路或其他大規模故障,可以從客戶端將流量排程到其他機房,也可以在CDN側將流量排程到不同的機房,資料流Flink ETL 鏈路可以分鐘級進入容災模式,切換到可用機房。

為了應對口播期間的流量洪峰,我們還準備了客戶端降級策略與服務端降級策略。其中客戶端降級策略可以動態的降低一定百分比使用者的埋點上報頻率,口播期間不上報,口播結束後逐步恢復。

在降級場景下,下游指標計算可以通過消費未降級的活動埋點分流估算整體指標。春節活動鏈路的順利保障標誌著資料流基於Flink搭建的ETL鏈路已經能提供較好的穩定性和可用性。

深度介紹Flink在位元組跳動資料流的實踐

02 - 資料流治理實踐

資料流比較常見的治理問題包括但不限於以下幾個:

  • 第一個是資料流穩定性治理中最常見的一個問題——Yarn單機問題導致Flink任務fail-over、反壓、消費能力下降。Yarn單機問題的型別有很多,比如:佇列負載不均、單機load高、其他程式導致CPU負載高、硬體故障等等。

  • 第二個問題是Kafka叢集負載不均導致Flink任務生產消費受到影響。

  • 第三個問題是埋點治理場景中無用埋點、異常埋點消耗大量計算儲存資源。

  • 針對單機問題,我們從Flink和Yarn兩個層面分別進行了優化,將單機load高導致的延遲減少了80%以上。

首先,Flink層面的優化。

在資料流ETL場景中,為了減少不必要的網路傳輸,Partitioner主要採用Rescale Partitioner。而Rescale Partitioner會使用Round-robin的方式傳送資料到下游部分Channel中,由於單機問題可能出現個別任務處理能力不足的情況,導致反壓,任務出現lag。

實際上資料發到下游任何一個任務都是可以的,最合理的策略應該根據下游任務的處理能力去傳送資料。

另一方面,我們注意到Flink Credit-based Flow Control反壓機制中,可以通過Backlog Size判斷下游任務的處理負載,那麼我們就可以將Round-robin傳送的方式修改為根據Channel的Backlog Size資訊選擇負載更低的下游Channel傳送的方式。

方案上線後佇列的負載更加均衡,CPU利用率提升10%。
深度介紹Flink在位元組跳動資料流的實踐

其次,Yarn層面的優化。

第一、佇列資源使用獨立Label佇列,避免高峰期和其他低優任務互相影響;

第二、Yarn節點上的DataNode偶發有頻寬打滿、CPU使用高的情況,影響節點上資料流Flink ETL 任務的穩定性,通過給DataNode設定網路限速並進行CPU綁核以避免DataNode對Flink程式的影響;

第三、Yarn反排程策略。目前位元組跳動Flink使用的Yarn GangScheduler排程策略會根據約束條件選擇性的獲取分配到的Yarn資源,在任務啟動時做到比較均衡的放置Container,但由於時間的推移,流量的變化等諸多因素,佇列還是可能會出現負載不均衡的情況。

反排程策略則是為了解決負載不均衡而生的二次排程機制。Yarn會定期檢查叢集中不再滿足原有約束的Container,並在這些Container所在的節點上篩選出需要重新排程的Container返回給Flink JobManager,Flink會重新排程這些Container。

重新排程會按照原有約束嘗試申請等量的可用資源,申請成功後進行遷移,申請不成功不做操作。

深度介紹Flink在位元組跳動資料流的實踐

針對Kafka叢集優化問題,我們自研來了儲存計算分離的MQ——BMQ,單GB流量成本下降50%。

在資料流這種大流量場景下使用Kafka,經常會遇到broker或者磁碟負載不均衡、磁碟壞掉等情況,進行擴容、機器替換時的運維操作會引起叢集Under Replica, 影響讀寫效能。除此之外,Kafka還有叢集規模瓶頸、多機房容災部署成本高等缺點。

為了優化這些問題,BMQ這款位元組跳動自研的儲存計算分離的MQ應運而生。

BMQ資料使用HDFS分散式儲存,每個partition被切分為多個segment,每個segment對應一個HDFS檔案,後設資料使用kv儲存,Proxy和Broker都是無狀態的,因此可以支援快速擴縮容,且沒有資料拷貝不會影響讀寫效能。受益於HDFS多機房容災部署能力,BMQ多機房容災部署變得比較簡單,資料同時寫入所有容災機房成功後才會向client返回成功,資料消費則是在每個機房本地消費,減少了跨機房頻寬,除此之外,由於基於HDFS儲存所需的副本數更少,單GB流量成本下降50%。

深度介紹Flink在位元組跳動資料流的實踐

針對埋點治理,我們從全產品開啟埋點管控、無用埋點監控&自助下線、埋點分級、風控能力建設四個點入手。

第一點,全產品開啟埋點管控。所有產品都需要先在流量平臺註冊埋點後設資料才能上報,這是從埋點接入流程進行的治理。

第二點,對於已上報的埋點,我們會通過埋點血緣,統計出已經沒有在使用的埋點,自動通知埋點負責人在平臺進行自助下線。埋點註冊和埋點下線完成後,都會通過埋點管控服務動態下發相關的配置資訊到埋點SDK和資料流Flink ETL任務中,從而保障未註冊埋點和無用埋點在上報或ETL環節被丟棄掉。

第三點是根據不同的用途對埋點進行分級,從而Dump到HDFS和數倉的時候可以按不同等級進行分割槽,不同等級的分割槽提供不同的TTL和就緒時間的保障。

最後一點則是針對異常流量,資料流ETL鏈路接入了風控系統,對埋點進行實時打標或過濾,防止異常流量造成資料傾斜、資料延遲、統計指標異常等問題。

深度介紹Flink在位元組跳動資料流的實踐

目前,Flink在位元組跳動資料流實踐中,已經可以做到計算層面的流批一體。接下來,我們還將計劃探索計算和儲存的流批一體,同時也會探索雲原生架構,實現資源的動態Rescale,提升資源利用率。我們也會一些高優鏈路保障上追求更高的SLA,比如保障端到端Exactly-once語義。

目前,現有的能力已經通過火山引擎大資料研發治理套件DataLeap對外開放。

歡迎關注位元組跳動資料平臺同名公眾號