汽車之家基於 Apache Flink 的跨資料庫實時物化檢視探索

ApacheFlink 發表於 2021-12-03
資料庫 Flink

本文介紹了汽車之家在基於 Flink 的實時物化檢視的一些實踐經驗與探索,並嘗試讓使用者直接以批處理 SQL 的思路開發 Flink Streaming SQL 任務。主要內容為:

  1. 系統分析與問題拆解
  2. 問題解決與系統實現
  3. 實時物化檢視實踐
  4. 限制與不足
  5. 總結與展望

img

前言

物化檢視這一功能想必大家都不陌生,我們可以通過使用物化檢視,將預先設定好的複雜 SQL 邏輯,以增量迭代的形式實時 (按照事務地) 更新結果集,從而通過查詢結果集來避免每次查詢複雜的開銷,從而節省時間與計算資源。事實上,很多資料庫系統和 OLAP 引擎都不同程度地支援了物化檢視。另一方面,Streaming SQL 本身就和物化檢視有著很深的聯絡,那麼基於 Apche Flink (下稱 Flink) SQL 去做一套實時物化檢視系統是一件十分自然而然的事情了。

本文介紹了汽車之家 (下稱之家) 在基於 Flink 的實時物化檢視的一些實踐經驗與探索,並嘗試讓使用者直接以批處理 SQL 的思路開發 Flink Streaming SQL 任務。希望能給大家帶來一些啟發,共同探索這一領域。

一、系統分析與問題拆解

Flink 在 Table & SQL 模組做了大量的工作,Flink SQL 已經實現了一套成熟與相對完備的 SQL 系統,同時,我們也在 Flink SQL 上有著比較多的技術和產品積累,直接基於 Flink SQL 本身就已經解決了構建實時物化系統的大部分問題,而唯一一個需要我們解決的問題是如何不重不漏地生成資料來源表對應的語義完備的 Changelog DataStream,包括增量和全量歷史兩部分。

雖然規約到只剩一個問題,但是這個問題解決起來還是比較困難的,那我們將這個問題繼續拆解為以下幾個子問題:

1.  載入全量資料;
2.  載入增量資料;
3.  增量資料與全量資料整合。


二、問題解決與系統實現

問題一:基於資料傳輸平臺的增量資料讀取

增量資料載入還是相對比較好解決的,我們直接複用實時資料傳輸平臺的基礎建設。資料傳輸平臺[1] 已經將 Mysql / SqlServer / TiDB 等增量資料以統一的資料格式寫入到特定的 Kafka Topic 中,我們只要獲取到對應的 Kafka Topic 就可以進行讀取即可。

問題二:支援 checkpoint 的全量資料載入

對於全量資料載入,我們先後寫了兩個版本。

第一版我們用 Legacy Source 寫了一套 BulkLoadSourceFunction,這一版的思路比較樸素,就是全量從資料來源表進行查詢。這個版本確實能完成全量資料的載入,但是問題也是比較明顯的。如果在 bulk load 階段作業發生了重啟,我們就不得不重新進行全量資料載入。對於資料量大的表,這個問題帶來的後果還是比較嚴重的。

對於第一版的固有問題,我們一直都沒有特別好的對策,直到 Flink-CDC[2] 2.0 的釋出。我們參考了 Flink-CDC 的全量資料載入階段支援 Checkpoint 的思路,基於 FLIP-27 開發了新的 BulkLoadSource。第二版不論在效能上還是可用性上,對比第一版都有了大幅提升。

問題三:基於全域性版本的輕量 CDC 資料整合演算法

這三個子問題中,問題三的難度是遠大於前面兩個子問題的。這個問題的樸素思路或許很簡單,我們只要按照 Key 快取全部資料,然後根據增量資料流來觸發 Changelog DataStream 更新即可。

事實上我們也曾按照這個思路開發了一版整合邏輯的運算元。這版運算元對於小表還是比較 work 的,但是對於大表,這種思路固有的 overhead 開始變得不可接受。我們曾用一張資料量在 12 億,大小約 120G 的 SqlServer 表進行測試,本身就巨大的資料再加上 JVM 上不可避免的膨脹,狀態大小變得比較誇張。經過這次測試,我們一致認為這樣粗放的策略似乎不適合作為生產版本釋出,於是我們不得不開始重新思考資料整合的演算法與策略。

在談論我們的演算法設計思路之前,我不得不提到 DBLog[3] 的演算法設計, 這個演算法的核心思路利用 watermark 對歷史資料進行標識,並和對應的增量資料進行合併,達到不使用鎖即可完成整個增量資料和歷史資料的整合,Flink-CDC 也是基於這個思路進行的實現與改進。在相關資料蒐集和分析的過程中,我們發現我們的演算法思路與 DBLog 的演算法的核心思路非常相似, 但是是基於我們的場景和情況進行了設計與特化。

首先分析我們的情況:

  • 增量資料需要來自於資料傳輸平臺的 Kafka Topic;
  • 增量資料的是 at least once 的;
  • 增量資料是存在全序版本號的。

結合上述情況進行分析,我們來規約一下這個演算法必須要達成的目標:

  • 保證資料的 Changelog Stream,資料完整,Event (RowKind) 語義完備
  • 保證該演算法的 overhead 是可控的;
  • 保證演算法實現的處理效能是足夠高效;
  • 保證演算法實現不依賴任何來自於 Flink 外部的系統或者功能。

經過大家的分析與討論後,我們設計出了一套資料整合的演算法,命名為 Global Version Based Pause-free Change-Data-Capture Algorithm

3.1 演算法原理

我們同時讀入 BulkLoadSource 的全量資料與 RealtimeChangelogSource 增量資料,並根據主鍵進行 KeyBy 與 Connect,而演算法的核心邏輯主要由之後的 KeyedCoProcess 階段完成。下面交待幾個關鍵的欄位值:

  • SearchTs:全量資料從資料來源查詢出來的時間戳;
  • Watermark:基於增量資料在資料庫裡產生的時間戳生成;
  • Version:全序版本號,全量資料是 0,即一定最小版本。

KeyedCoProcess 收到全量資料後,不會直接傳送,而是先快取起來,等到 Watermark 的值大於該 SearchTs 後傳送並清除對應 version0 版本資料的快取。在等待的期間,如果有對應的 Changlog Data,就將被快取的 Version0 全量資料丟棄,然後處理 Changelog Data 併傳送。在整個資料處理的流程中,全量資料和增量資料都是同時進行消費與處理的,完全不需要引入暫停階段來進行資料的整合。

image

             增量資料在全量資料傳送 watermark 之前到來,只傳送增量資料即可,全量資料直接丟棄        


image

             全量資料傳送 watermark 到達後,仍未有對應的增量資料,直接傳送全量資料


3.2 演算法實現

我們決定以 Flink Connector 的形式開展演算法的實現,我們以接入 SDK 的名字 Estuary 為該 Connector 命名。通過使用 DataStreamScanProvider,來完成 Source 內部運算元間的串聯,Source 的運算元組織如下圖 (chain 到一起的運算元已拆開展示)。

image

  • BulkLoadSource / ChangelogSource 主要負責資料的讀入和統一格式處理;
  • BulkNormalize / ChangelogNormalize 主要是負責處理資料執行時資訊的新增與覆蓋,主鍵語義處理等工作;
  • WatermarkGenerator 是針對演算法工作需求定製的 Watermark 生成邏輯的運算元;
  • VersionBasedKeyedCoProcess 就是核心的處理合並邏輯和 RowKind 語義完備性的運算元。

演算法實現的過程中還是有很多需要優化或者進行權衡的點。全量資料進入 CoProcess 資料後,會首先檢查當前是否處理過更大版本的資料,如果沒有的話才進行處理,資料首先會被存入 State 中並根據 SearchTs + T (T 是我們設定的固有時延) 註冊 EventTimeTimer。如果沒有高版本的資料到來,定時器觸發傳送 Version 0 的資料,否則直接拋棄改為傳送 RowKind 語義處理好的高版本增量資料。

另一方面,避免狀態的無限增長,當系統判定 BulkLoad 階段結束後,會結束對相關 Flink State 的使用,存在的 State 只要等待 TTL 過期即可。

另外,我們針對在資料同步且下游 Sink 支援 Upsert 能力的場景下,開發了特別優化的超輕量模式,可以以超低的 overhead 完成全量+增量的資料同步

開發完成後,我們的反覆測試修改與驗證,完成 MVP 版本的開發。

三、實時物化檢視實踐

MVP 版本釋出後,我們與使用者同學一起,進行了基於 Flink 的物化檢視試點。

1. 基於多資料來源複雜邏輯的 Data Pipeline 實時化

下面是使用者的一個真實生產需求:有三張表,分別來自於 TiDB /。SqlServer / Mysql,資料行數分別為千萬級 / 億級 / 千萬級,計算邏輯相對複雜,涉及到去重,多表 Join。原有通過離線批處理產生 T+1 的結果表。而使用者希望儘可能降低該 Pipeline 的延遲。

由於我們使用的 TiCDC Update 資料尚不包含 -U 部分,故 TiDB 表的整合演算法還是採取 Legacy Mode 進行載入。

我們與使用者溝通,建議他們以批處理的思路去編寫 Flink SQL,把結果的明細資料的資料輸出到 StarRocks 中。使用者也在我們的協助下,較為快速地完成了 SQL 的開發,任務的計算拓補圖如下:

image

結果是相當讓人驚喜的!我們成功地在保證了資料準確性的情況下,將原來天級延遲的 Pipeline 降低至 10s 左右的延遲。資料也從原來查詢 Hive 變為查詢 StarRocks,不論從資料接入,資料預計算,還是資料計算與查詢,實現了全面的實時化。另一方面,三張表每秒的增量最大不超過 300 條,且該任務不存在更新放大的問題,所以資源使用相當的少。根據監控反饋的資訊,初始化階段完成後,整個任務 TM 部分只需要使用 1 個 Cpu (on YARN),且 Cpu 使用常態不超過 20%。對比原來批處理的資源使用,無疑也是巨大提升。

2. 資料湖場景優化

正如上文提到的,對於資料同步,我們做了專門的優化。只需要使用專用的 Source 表,就可以一鍵開啟歷史資料 + 增量資料資料同步,大大簡化了資料同步的流程。我們目前嘗試使用該功能將資料同步至基於 Iceberg 的資料湖中,從資料同步層面大幅提升資料新鮮度。

image

四、限制與不足

雖然我們在這個方向的探索取得了一定成果,但是仍有一定的限制和不足。

1. 伺服器時鐘的隱式依賴

仔細閱讀上面演算法原理,我們會發現,不論是 SearchTs 的生成還是 Watermark 的生成,實際上最後都依賴了伺服器系統的時鐘,而非依賴類似 Time Oracle 機制。我們雖然演算法實現上引入固有延遲去規避這個問題,但是如果伺服器出現非常嚴重時鐘不一致,超過固有延遲的話,此時 watermark 是不可靠的,有可能會造成處理邏輯的錯誤。

經確認,之家伺服器時鐘會進行校準操作。

2. 一致性與事務

事實上我們目前這套實現沒有任何事務相關的保證機制,僅能承諾結果的最終一致性,最終一致性其實是一種相當弱的保證。就拿上文提到的例子來說,如果其中一張表存在 2 個小時的消費延遲,另一張表基本不存在延遲,這個時候兩表 Join 產生的結果其實是一種中間狀態,或者說對於外部系統應該是不可見的。

為了完成更高的一致性保證,避免上面問題的產生,我們自然會想到引入事務提交機制。然而目前我們暫時沒有找到比較好的實現思路,但是可以探討下我們目前的思考。

2.1 如何定義事務

事務這個概念想必大家或多或少都有認識,在此不多贅述。如何資料庫系統內部定義事務是一件特別自然且必要的事情,但是如何在這種跨資料來源場景下定義事務,其實是一件非常困難的事情。還是以上文的例子來展開,我們能看到資料來源來自各種不同資料庫,我們其實對於單表記錄了對應的事務資訊,但是確實沒有辦法定義來自不同資料來源的統一事務。我們目前的樸素思路是根據資料產生的時間為基準,結合 checkpoint 統一劃定 Epoch,實現類似 Epoch-based Commit 的提交機制。但是這樣做又回到前面提到的問題,需要對伺服器時間產生依賴,無法從根源保證正確性。

2.2 跨表事務

對於 Flink 物化檢視一致性提交這個問題,TiFlink[4] 已經做了很多相關工作。但是我們的 Source 來自不同資料來源,且讀取自 Kafka,所以問題變得更為複雜,還是上面提到的例子,兩張表 Join 過後,如果想保證一致性,不只是 Source 和 Sink 運算元,整個關係代數運算元體系都需要考慮引入事務提交的概念和機制,從而避免中間狀態的對外部系統的釋出。

3. 更新放大

這個問題其實比較好理解。現在有兩張表 join,對於左表的每一行資料,對應右表都有 n (n > 100) 條資料與之對應。那麼現在更新左表的任意一行,都會有 2n 的更新放大。

4. 狀態大小

目前整套演算法在全量同步階段的 Overhead 雖然可控,但是仍有優化空間。我們目前實測,對於一張資料量在 1 億左右的表,在全量資料階段,需要峰值最大為 1.5G 左右的 State。我們打算在下個版本繼續優化狀態大小,最直接的思路就是 BulkSource 通知 KeyedCoProcess 哪些主鍵集合是已經處理完畢的,這樣可以使對應的 Key 提早進入全量階段完成模式,從而進一步優化狀態大小。

五、總結與展望

本文分析了基於 Flink 物化檢視實現的問題與挑戰,著重介紹了處理生成完整的 Changelog DataStream 的演算法與實現和在業務上的收益,也充分闡述了目前的限制與不足。

雖然這次實踐的結果稱不上完備,存在一些問題亟待解決,但是我們仍看到了巨大的突破與進步,不論是從技術還是業務使用上。我們充分相信未來這項技術會越來越成熟,越來越被更多人認可和使用,也通過此次探索充分驗證了流處理和批處理的統一性。

我們目前的實現還處在早期版本,仍有著工程優化和 bug fix 的空間與工作 (比如前文提到的兩表的推進的 skew 太大問題,可以嘗試引入 Coordinator 進行調節與對齊),但是相信隨著不斷的迭代與發展,這項工作會變得越來越穩固,從而支撐更多業務場景,充分提升資料處理的質量與效率!

特別鳴謝張茄子和雲邪老師的幫助與勘誤。

引用

[1] http://mp.weixin.qq.com/s/KQH...

[2] http://github.com/ververica/f...

[3] http://arxiv.org/pdf/2010.125...

[4] http://zhuanlan.zhihu.com/p/4...


更多 Flink 相關技術問題,可掃碼加入社群釘釘交流群;

第一時間獲取最新技術文章和社群動態,請關注公眾號~

img