本文整理自 BIGO Staff Engineer 鄒雲鶴在 Flink Forward Asia 2021 的分享。主要內容包括:
- 業務背景
- 落地實踐 & 特色改進
- 應用場景
- 未來規劃
一、業務背景
BIGO 是一家面向海外的以短視訊直播業務為主的公司, 目前公司的主要業務包括 BigoLive (全球直播服務),Likee (短視訊創作分享平臺),IMO (免費通訊工具) 三部分,在全球範圍內擁有 4 億使用者。伴隨著業務的發展,對資料平臺處理能力的要求也是越來越高,平臺所面臨的問題也是日益凸顯,接下來將介紹 BIGO 大資料平臺及其所面臨的問題。BIGO 大資料平臺的資料流轉圖如下所示:
使用者在 APP,Web 頁面上的行為日誌資料,以及關聯式資料庫的 Binlog 資料會被同步到 BIGO 大資料平臺訊息佇列,以及離線儲存系統中,然後通過實時的,離線的資料分析手段進行計算,以應用於實時推薦、監控、即席查詢等使用場景。然而存在以下幾個問題:
- OLAP 分析平臺入口不統一:Presto/Spark 分析任務入口並存,使用者不清楚自己的 SQL 查詢適合哪個引擎執行,盲目選擇,體驗不好;另外,使用者會在兩個入口同時提交相同查詢,以更快的獲取查詢結果,導致資源浪費;
- 離線任務計算時延高,結果產出太慢:典型的如 ABTest 業務,經常計算到下午才計算出結果;
- 各個業務方基於自己的業務場景獨立開發應用,實時任務煙囪式的開發,缺少資料分層,資料血緣。
面對以上的問題,BIGO 大資料平臺建設了 OneSQL OLAP 分析平臺,以及實時數倉。
- 通過 OneSQL OLAP 分析平臺,統一 OLAP 查詢入口,減少使用者盲目選擇,提升平臺的資源利用率;
- 通過 Flink 構建實時數倉任務,通過 Kafka/Pulsar 進行資料分層;
- 將部分離線計算慢的任務遷移到 Flink 流式計算任務上,加速計算結果的產出;
另外建設實時計算平臺 Bigoflow 管理這些實時計算任務,建設實時任務的血緣關係。
二、落地實踐 & 特色改進
2.1 OneSQL OLAP 分析平臺實踐和優化
OneSQL OLAP 分析平臺是一個集 Flink、Spark、Presto 於一體的 OLAP 查詢分析引擎。使用者提交的 OLAP 查詢請求通過 OneSQL 後端轉發到不同執行引擎的客戶端,然後提交對應的查詢請求到不同的叢集上執行。其整體架構圖如下:
該分析平臺整體結構從上到下分為入口層、轉發層、執行層、資源管理層。為了優化使用者體驗,減少執行失敗的概率,提升各叢集的資源利用率,OneSQL OLAP 分析平臺實現了以下功能:
- 統一查詢入口:入口層,使用者通過統一的 Hue 查詢頁面入口以 Hive SQL 語法為標準提交查詢;
- 統一查詢語法:集 Flink、Spark、Presto 等多種查詢引擎於一體,不同查詢引擎通過適配 Hive SQL 語法來執行使用者的 SQL 查詢任務;
- 智慧路由:在選擇執行引擎的過程中,會根據歷史 SQL 查詢執行的情況 (在各引擎上是否執行成功,以及執行耗時),各叢集的繁忙情況,以及各引擎對該 SQL 語法的是否相容,來選擇合適的引擎提交查詢;
- 失敗重試:OneSQL 後臺會監控 SQL 任務的執行情況,如果 SQL 任務在執行過程中失敗,將選擇其他的引擎執行重試提交任務;
如此一來,通過 OneSQL OLAP 分析平臺,BIGO 大資料平臺實現了 OLAP 分析入口的統一,減少使用者的盲目選擇,同時充分利用各個叢集的資源,減少資源空閒情況。
2.1.1 Flink OLAP 分析系統建設
在 OneSQL 分析平臺上,Flink 也作為 OLAP 分析引擎的一部分。Flink OLAP 系統分成兩個組成部分:Flink SQL Gateway 和 Flink Session 叢集;SQL Gateway 作為 SQL 提交的入口,查詢 SQL 經過 Gateway 提交到 Flink Session 叢集上執行,同時獲取 SQL 執行查詢的進度,以及返回查詢的結果給客戶端。其執行 SQL 查詢的流程如下:
首先使用者提交過來的 SQL,在 SQL Gateway 進行判斷:是否需要將結果持久化寫入到 Hive 表,如果需要,則會先通過 HiveCatalog 的介面建立一個 Hive 表,用於持久化查詢任務的計算結果;之後,任務通過 SQL Gateway 上執行 SQL 解析,設定作業執行的並行度,生成 Pipeline 並提交到 Session 叢集上執行。
為了保證整個 Flink OLAP 系統的穩定性,以及高效的執行 SQL 查詢,在這個系統中,進行了以下功能增強:
穩定性:
- 基於 zookeeper HA 來保證 Flink Session 叢集的可靠性,SQL Gateway 監聽 Zookeeper 節點,感知 Session 叢集;
- 控制查詢掃描 Hive 表的資料量,分割槽個數,以及返回結果資料量,防止 Session 叢集的 JobManager,TaskManager 因此出現 OOM 情況;
效能:
- Flink Session 叢集預分配資源,減少作業提交後申請資源所需的時間;
- Flink JobManager 非同步解析 Split,Split 邊解析任務邊執行,減少由於解析 Split 阻塞任務執行的時間;
- 控制作業提交過程中掃描分割槽,以及 Split 最大的個數,減少設定任務並行所需要的時間;
Hive SQL 相容:
針對 Flink 對於 Hive SQL 語法的相容性進行改進,目前針對 Hive SQL 的相容性大致為 80%;
監控告警:
監控 Flink Session 叢集的 JobManager,TaskManager,以及 SQL Gateway 的記憶體,CPU 使用情況,以及任務的提交情況,一旦出現問題,及時告警和處理;
2.1.2 OneSQL OLAP 分析平臺取得的成果
基於以上實現的 OneSQL OLAP 分析平臺,取得了以下幾個收益:
- 統一查詢入口,減少使用者的盲目選擇,使用者執行出錯率下降 85.7%,SQL 執行的成功率提升 3%;
- SQL 執行時間縮短 10%,充分利用了各個叢集的資源,減少任務排隊等待的時間;
- Flink 作為 OLAP 分析引擎的一部分,實時計算叢集的資源利用率提升了 15%;
2.2 實時數倉建設和優化
為了提升 BIGO 大資料平臺上某些業務指標的產出效率,以及更好的管理 Flink 實時任務,BIGO 大資料平臺建設了實時計算平臺 Bigoflow,並將部分計算慢的任務遷移到實時計算平臺上,通過 Flink 流式計算的方式來執行,通過訊息佇列 Kafka/Pulsar 來進行資料分層,構建實時數倉;在 Bigoflow 上針對實時數倉的任務進行平臺化管理,建立統一的實時任務接入入口,並基於該平臺管理實時任務的後設資料,構建實時任務的血緣關係。
2.2.1 建設方案
BIGO 大資料平臺主要基於 Flink + ClickHouse 建設實時數倉,大致方案如下:
按照傳統資料倉儲的資料分層方法,將資料劃分成 ODS、DWD、DWS、ADS 等四層資料:
- ODS 層:基於使用者的行為日誌,業務日誌等作為原始資料,存放於 Kafka/Pulsar 等訊息佇列中;
- DWD 層:這部分資料根據使用者的 UserId 經過 Flink 任務進行聚合後,形成不同使用者的行為明細資料,儲存到 Kafka/Pulsar 中;
- DWS 層:使用者行為明細的 Kafka 流表與使用者 Hive/MySQL 維表進行流維表 JOIN,然後將 JOIN 之後產生的多維明細資料輸出到 ClickHouse 表中;
- ADS 層:針對 ClickHouse 中多維明細資料按照不同維度進行彙總,然後應用於不同的業務中。
按照以上方案建設實時資料倉儲的過程中,遇到了一些問題:
- 將離線任務轉為實時計算任務後,計算邏輯較為複雜 (多流 JOIN,去重),導致作業狀態太大,作業出現 OOM (記憶體溢位) 異常或者作業運算元背壓太大;
- 維表 Join 過程中,明細流表與大維表 Join,維表資料過多,載入到記憶體後 OOM,作業失敗無法執行;
- Flink 將流維表 Join 產生的多維明細資料寫入到 ClickHouse,無法保證 Exactly-once,一旦作業出現 Failover,就會導致資料重複寫入。
2.2.2 問題解決 & 優化
優化作業執行邏輯,減小狀態
離線的計算任務邏輯較為複雜,涉及多個 Hive 表之間的 Join 以及去重操作,其大致邏輯如下:
當將離線的作業轉為 Flink 的流式任務之後,原先離線 Join 多個 Hive 表的場景就轉變為 Join 多個 Kafka Topic 的場景。 由於 Join 的 Kafka topic 的流量較大,且 Join 的視窗時間較長 (視窗最長的為 1 天),當作業執行一段時間內,Join 運算元上就積累了大量的狀態 (一小時後狀態就接近 1T),面對如此大的狀態,Flink 作業採取 Rocksdb State Backend 來存放狀態資料,但是仍然避免不了 Rocksdb 記憶體使用超過導致被 YARN kill 的問題,或者是 Rocksdb State 上存的狀態太多,吞吐下降導致作業嚴重背壓。
針對這個問題,我們將這多個 Topic,按照相同的 Schema 進行 Unoin all 處理,得到一個大的資料流,然後在這個大的資料流中,再根據不同事件流的 event_id 進行判斷,就能知道這條資料來自哪一個事件流的 Topic,再進行聚合計算,獲取對應事件流上的計算指標。
這樣一來,通過 UNION ALL 代替 JOIN,避免了因為 JOIN 計算帶來的大 State 帶來的影響。
另外,在計算任務中還存在有比較多的 count distinct 計算,類似如下:
select
count(distinct if(events['a'] = 1, postid, null))
as cnt1,
count(distinct if(events['b'] = 1, postid, null))
as cnt2
……
count(distinct if(events['x'] = 1, postid, null))
As cntx
From table_a
Group by uid
這些 count distinct 計算在同一個 group by 中,並基於相同的 postid 進行去重計算,因而可以讓這些 distinct state 可以共享一組 key 來進行去重計算,那麼就可以通過一個 MapState 來儲存這若干個 count distinct 的狀態,如下:
這些 count distinct 函式去重的 key 相同,因而可以共享 MapState 中的 key 值,從而優化儲存空間;而 Mapstate 的 Value 是 Byte 陣列,每個 Byte 8 個 bit,每個 bit 為 0 或者 1,第 n 個 bit 對應了 n 個 count distinct 函式在該 key 上的取值:1 表示該 count disitnct 函式在對應的 key 上需要進行計數,0 表示不需要計數;當計算聚合結果的時候,則將所有 key 第 n 位的數字相加,即為第 n 個 count distinct 的取值,這樣一來,就更進一步節約了狀態的儲存空間。
通過以上優化,成功的將 ABTest 的離線任務遷移到 Flink 流式計算任務上,將作業的狀態控制在 100GB 以內,讓作業正常的執行起來。
流維表 JOIN 優化
生成多維明細寬表的過程中,需要進行流維表 JOIN, 使用了 Flink Join Hive 維表的功能:Hive 維表的資料會被載入到任務的 HashMap 的記憶體資料結構中,流表中的資料再根據 Join Key 與 HashMap 中的資料進行 Join。但是面對上億,十億行的 Hive 大維表,載入到記憶體的資料量太大,很容易導致 OOM (記憶體溢位)。針對以上問題,我們將 Hive 大維表按照 Join Key 進行 Hash 分片,如下圖:
這樣一來,Hive 大維表的資料經過 Hash 函式計算後分布到 Flink 作業的不同並行子任務的 HashMap 中,每個 HashMap 只存放大維表的一部分資料,只要作業的並行度夠大,就能夠將大維表的資料拆分成足夠多份,進行分片儲存;對於一些太大的維表,也可以採取 Rocksdb Map State 來儲存分片資料。
Kafka 流表中的資料,當要下發到不同的 subtask 上進行 Join 時,也通過相同的 Join Key 按照相同的 Hash 函式進行計算,從而將資料分配到對應的 subtask 進行 Join,輸出 Join 後的結果。
通過以上優化,成功 Join 了一些 Hive 大維表任務來執行流維表 Join 計算,最大的維表超過 10 億行。
ClickHouse Sink 的 Exactly-Once 語義支援
將流維表 Join 生成的多維明細資料輸出到 ClickHouse 表的過程中,由於社群的 ClickHouse 不支援事務,所以沒辦法保證資料 sink 到 ClickHouse 過程中的 Exactly-Once 語義。在此過程中,一旦出現作業 Failover,資料就會重複寫入到 ClickHouse。
針對這個問題,BIGO ClickHouse 實現了一個二階段提交事務機制:當需要寫入資料到 ClickHouse 時,可以先設定寫入的模式為 temporary,表明現在寫入的資料是臨時資料;當資料執行插入完成後,返回一個 Insert id,然後根據該 Insert id 執行 Commit 操作,那麼臨時資料就轉為正式資料。
基於 BIGO ClickHouse 的二階段提交事務機制,並結合 Flink 的 checkpoint 機制,實現了一個 ClickHouse Connector,保證 ClickHouse Sink 的 Exactly Once 寫入語義,如下:
- 在正常寫入的情況下,Connector 隨機選擇 ClickHouse 的某一個 shard 寫入,根據使用者配置寫單副本,或者雙副本來執行 insert 操作,並記錄寫入後的 insert id;在兩次 checkpoint 之間就會有多次這種 insert 操作,從而產生多個 insert id,當 checkpoint 完成時,再將這些 insert id 批量提交,將臨時資料轉為正式資料,即完成了兩次 checkpoint 間資料的寫入;
- 一旦作業出現 Failover,Flink 作業 Failover 重啟完成後,將從最近一次完成的 checkpoint 來恢復狀態,此時 ClickHouse Sink 中的 Operator State 可能會包含上一次還沒有來得及提交完成的 Insert id,針對這些 insert id 進行重試提交;針對那些資料已經寫入 ClickHouse 中之後,但是 insert id 並沒有記錄到 Opeator State 中的資料,由於是臨時資料,在 ClickHouse 中並不會被查詢到,一段時間後,將會由 ClickHouse 的過期清理機制,被清理掉,從而保證了狀態回滾到上一次 checkpoint 之後,資料不會重複。
通過以上機制,成功保證了資料從 Kafka 經過 Flink 計算後寫入到 ClickHouse 整個鏈路中端到端的 Exactly-Once 語義,資料不重複也不丟失。
2.2.3 平臺建設
為了更好的管理 BIGO 大資料平臺的實時計算任務,公司內部建設了 BIGO 實時計算平臺 Bigoflow,為使用者提供統一的 Flink實時任務接入,平臺建設如下:
- 支援 Flink JAR、SQL、Python 等多種型別作業;支援不同的 Flink 版本,覆蓋公司內部大部分實時計算相關業務;
- 一站式管理:集作業開發、提交、執行、歷史展示、監控、告警於一體,便於隨時檢視作業的執行狀態和發現問題;
- 血緣關係:方便查詢每個作業的資料來源、資料目的、資料計算的來龍去脈。
三、應用場景
3.1 Onesql OLAP 分析平臺應用場景
Onesql OLAP 分析平臺在公司內部的應用場景是:應用於 AdHoc 查詢,如下:
使用者通過 Hue 頁面提交的 SQL,通過 OneSQL 後端轉發給 Flink SQL Gateway,並提交到 Flink Session 叢集上執行查詢任務,Flink SQL Gateway 獲取查詢任務的執行進度返回給 Hue 頁面,並返回查詢結果。
3.2 實時資料倉儲應用場景
實時資料倉儲應用場景目前主要是 ABTest 業務,如下:
使用者的原始行為日誌資料經過 Flink 任務聚合後生成使用者明細資料,然後與維表資料進行流維表 JOIN,輸出到 ClickHouse 生成多維明細寬表,按照不同維度彙總後,應用於不同的業務。通過改造 ABTest 業務,將該業務的結果指標的生成時間提前了 8 個小時,同時減少了使用資源一倍以上。
四、未來規劃
為了更好的建設 OneSQL OLAP 分析平臺以及 BIGO 實時資料倉儲,實時計算平臺的規劃如下:
- 完善 Flink OLAP 分析平臺,完善 Hive SQL 語法支援,以及解決計算過程中出現的 JOIN 資料傾斜問題;
- 完善實時數倉建設,引入資料湖技術,解決實時數倉中任務資料的可重跑回溯範圍小的問題;
- 基於 Flink 打造流批一體的資料計算平臺。
更多 Flink 相關技術問題,可掃碼加入社群釘釘交流群
第一時間獲取最新技術文章和社群動態,請關注公眾號~