基於 Flink CDC 的實時同步系統

ApacheFlink發表於2023-03-17

摘要:本文整理自科傑科技大資料架構師張軍,在 FFA 資料整合專場的分享。本篇內容主要分為四個部分:

  1. 功能概述
  2. 架構設計
  3. 技術挑戰
  4. 生產實踐

點選檢視直播回放和演講 PPT

1

科傑科技是專門做大資料服務的供應商,目前的客戶包括能源、金融、證券等各個行業。科傑科技產品的底層是基於湖倉一體的基礎資料平臺,在資料平臺之上有離線、實時、機器學習等各種系統。我主要負責基於 Flink、Iceberg、K8s 的底層基礎設施建設。今天將主要和大家分享,上圖中框出來的子系統,即基於 Flink CDC 的實時資料同步系統。

一、功能概述

2

我們系統的主要的功能有如下幾個:

  • 視覺化操作。我們做了後臺的管理系統,是希望使用者在不懂任何程式碼的情況下,透過點選滑鼠就能配置出同步任務做資料同步。
  • 支援整庫同步、多表同步。
  • DDL 支援:源端的 Schema 的變更也要同步到目標端。
  • 資料庫、表、欄位對映。
  • 豐富的資料來源支援。目前輸入端支援四種常見的關係型資料庫,MySQL、Postgre、SQL Server、Oracle,輸出端除了這四個資料庫之外,還包含 Kafka 和 Iceberg。
  • 豐富的資料型別支援。對輸入端的四種關係型資料庫,我們常用的所有資料型別都會支援,包括二進位制型別。
  • UDF 函式、過濾條件。UDF 函式是指我們在同步過程中,做一些資料轉換。過濾條件是指我們會在同步的過程中,加一些過濾條件,只同步想要的資料。
  • 選取欄位、新增變數欄位。選取欄位是指使用者可以選擇想要的欄位進行同步。新增變數是指在同步的過程中,可以手工新增一些欄位,比如時間戳或者表名等。

3

市面上有很多實時同步的系統,最終我們選用了 Flink CDC 做實時同步系統的底層技術架構。主要是因為 Flink CDC 有一些獨有的優勢,包括全量同步、增量同步、全量+增量同步,還有底層基於 Flink 做的分散式計算引擎。

透過 Flink CDC 這套架構,想實現我們現有產品的需求,目前來看還有一些不足。

  • DDL 的支援:PostgreSQL、Oracle 資料庫無法獲取 Schema 變更的事件,無法捕獲相應的 DDL 操作。
  • 整庫同步:透過 Flink CDC 的 API 可以捕獲表結構的變更資訊,但是現有的 Flink Connector 無法將新增的表、欄位寫入目標端。
  • 需要預知 Schema:Flink 任務需要提前知道表結構的 Schema,然後構建任務,無法實現不重啟的情況下動態處理新增表或者欄位。

二、架構設計

4

接下來從技術角度給大家分享一下我們系統的設計架構,從上圖中可以看到,一共分為三層。

最上面一層是輸入端。基於 Flink CDC API 的方式讀資料庫進行資料抽取,然後把這些資料和 Schema 的資訊發到中間的 Kafka,Kafka 是我們的中間緩衝層。最下面一層是輸出端,會從 Kafka 讀取輸入端輸入的資料。

在輸出端這一層可以看到,首先進行過濾,常用的 SQL 表示式都可以做過濾條件。過濾後對欄位應用一些 UDF,比如資料脫敏、加密等等。接下來根據 DB 和 Table 對資料進行 Keyby 分組,然後使用 KeyedProcessFunction 函式對每個表的資料進行一些處理,比如建立表、新增或者修改欄位、插入資料等等。

當配置完任務之後,最後我們分別把 Source 和 Sink 的任務提交到運維中心,運維中心會對任務進行啟動、停止、檢視統計指標、檢視任務狀態等一系列操作。最後我們的任務支援在 Yarn 和 K8s 上執行,使用者可以根據自己的情況進行選擇。

5

在後臺管理系統,使用者可以透過配置輸入端和輸出端,配置需要同步的任務。任務會生成兩個配置檔案,分別是輸入端的配置檔案和輸出端的配置檔案,然後這兩個配置檔案會分別作為輸入端和輸出端的啟動引數傳給兩個 Flink 任務。

6

這部分主要是想分享下,對於無法獲取 DDL 事件的情況我們該如何處理呢?

其實有一些資料庫,比如 MySQL,是可以透過 Flink CDC 來獲取 Schema 的變更資訊的,但是為了程式碼的邏輯統一,同時適配 Flink CDC 拿不到 Schema 變更的資料庫。我們做了程式碼統一的處理,用一套架構完成資料和 Schema 的抽取和封裝。

我們透過 JDBC 的方式,從源資料庫把 Schema 的資訊查出來,放到 Flink 的 State 裡。當下一條資料來的時候,跟 State 裡面的 Schema 資料進行對比。相同就不做任何處理,不同就再次查詢一下 Schema 的資訊,更新到 Flink State 裡。同時將從 Flink CDC 拿到的資料和這條資料對應的 Schema 資訊,封裝成訊息體,傳送給中間層的 Kafka。從 Schema 讀取的資訊包含資料的型別、長度、精度,是否是主鍵等等,格式和 debezium-json 差不多。

Kafka 緩衝層可以用來實現以下幾個功能。

在解耦方面:

  • 將 Source 和 Sink 解耦。
  • 多個輸出端避免重複抽取。比如我想從 MySQL 抽取一些資料,把它同步到 Iceberg 做一些離線的分析。同時又同步到 Kafka,做一些實時的資料處理。這種情況就可以從源端只抽取一次,減少對源端資料庫的壓力。
  • Sink 出現故障避免 Source 阻塞,類似 flume 的 channel 的功能。

在 DB 對應 Topic 方面:

  • 一個資料庫裡面的資料抽取到一個 Topic。
  • 每個 Topic 一個 Partition。
  • 單表重放順序有保證。

7

輸出端和輸入端一樣,讀取後端生成的配置檔案作為它的引數,然後使用一些過濾條件,UDF 轉換條件等等,從 Kafka 讀取資料,進行資料處理。

在資料處理的時候,因為每個輸出源的處理邏輯不一樣,所以分成以下三類。

  • 寫入 RDBMS。透過 JDBC 來運算元據庫,包含 DDL、DML。
  • 寫入 Iceberg。重寫 Flink 寫入 Icebrg 邏輯,使用原始 API 寫入資料,Commit Snapshot。
  • 寫入 Kafka。使用 Flink Kafka Connector 寫入 Kafka。

8

運維中心可以對資料進行如下處理:

  • 任務的管理:包含任務的啟動、停止、暫停等等。
  • 檢視指標:監控一些資料,包含同步任務的資料條數和資料大小。
  • 配置監控報警:同步任務發生故障時,傳送報警,包括郵件、簡訊等等。
  • 檢視日誌:檢視任務啟動的日誌、任務執行過程中的日誌。

三、技術挑戰

9

下面列舉一些主要的技術挑戰。

  • 讀取增量 Schema:獲取源端新增的表、欄位以及資料資訊(比如 Flink CDC 無法獲取 PostgreSQL 資料庫的 Schema 變更事件)。
  • 升級 debezium: 修改 Flink CDC 原始碼, 升級 debezium 至最新版,獲取 Oracle 新增表、欄位事件。
  • SQL 形式過濾條件:支援 SQL 形式的過濾條件,and、or、in、>、<、between 等常用的表示式。
  • 不重啟支援動態 Schema: 不重啟 Flink 任務,支援動態 Schema 及各種 DML 將資料寫入目標端。
  • 重構 Flink 寫入 Iceberg:沒有使用現有的 Flink Datastream API 寫入 Iceberg,重新使用 Iceberg 最底層 API 建立、修改表,插入、修改、刪除資料。
  • 複雜業務邏輯:支援複雜的業務場景,要保證資料的正確性。

10

這是我們在開發過程中,輸出端遇到的第一個問題,也就是 SQL 條件的過濾。大家可能乍一聽覺得很簡單,加一個 where 條件就行了,但 Flink 任務在做資料同步時,它要求輸入端和輸出端的 Schema 需要預先提前知道,且它是固定不變的,但是我們的情況有一些不同,比如對於整庫同步的過程中,使用者新增了一些表,或者在表同步的過程中,新增了一些欄位,Flink 現有的 collector 無法識別這些新增的資訊,無法在未知的欄位上新增 where 條件。那麼我們要如何解決這個問題呢?

我們傳送到中間 Kafka 緩衝層的資料格式和 debezium-json 的格式差不多,資料主要儲存在 payload.after 和 payload.before 裡面,這裡面的資料的格式是 map 型別,它的 key 是字串,value 是 object 型別的資料,但是這個格式我們無法把它對映成 Flink SQL,因為 object 型別在 Flink CDC 裡面沒有對應的型別,所以我們把 object 型別對映成了 string 型別,並對 SQL 進行了一些轉換。使用 Flink SQL 解析器把 where 條件進行解析,然後重新生成新的過濾條件。

比如我們原始的過濾 SQL 是這樣的:

id between 1 and 3.5

經過我們的重構,變成了下面這個形式:

cast(payload.after['id'] as DECIMAL(2,1)) BETWEEN ASYMMETRIC 1 AND 3.5

11

資料經過 where 條件的過濾之後,並且經過 UDF 函式轉換進入 KeyedProcessFunction 函式進行處理。第一步先判斷輸出端的目標庫和目標表是否已經存在。在沒有存在的情況下,用純 JDBC 的方式拼接 SQL 執行 DDL,建立資料庫和表。然後進行資料處理,為了提高效能。我們把資料放到佇列裡,當佇列達到一定的閾值後,進行 flush 操作,把資料批次寫入資料庫。

在這個同步過程中,對於 Schema 的處理和 Source 端一樣,把獲取的 Schema 資訊放到 State 裡,每來一條資料進行一次 Schema 對比。如果發生了變更,就能證明資料發生了 DDL 的操作。這個時候要刷資料,把佇列裡的資料 flush 到資料庫,然後執行 DDL,執行完 DDL 之後重新拼接一個 INSERT INTO 的 SQL 執行新插入的資料。透過這種方式實現不重啟 Flink 任務的情況下,同時支援 DDL(create、alter)和 DML(insert、update、delete)等一系列操作。

12

因為 Iceberg 無法用純 JDBC 的方式寫入,所以它無法跟關係型資料結合到一起。因此 Flink 寫入 Iceberg 會遇到以下的一些問題。

  • Flink SQL 不支援 DDL。比如 Flink SQL 無法支援 Alter Table 的 DDL 語法。
  • Flink SQL 需預知 Schema。使用 Flink SQL 寫入 Iceberg 表,需要提前知道表的 Schema 資訊,且無法處理新增欄位。
  • DataStream 需預知 Schema。如果使用 API 寫入,也會和 Flink SQL 一樣遇到同樣的問題,寫入也是需要提前預知表的 Schema 資訊。
  • 提交 Snapshot。Flink 寫入 Iceberg 是每次 Checkpoint 提交快照,但是我們需要自己控制,需要在發生 DDL 的時候觸發提交。

13

我們發現 Flink 不管用 SQL 還是 API 的方式,都無法完成我們的需求,所以我們從更底層的角度來考慮實現方法,最後使用 Iceberg 很底層的 API 來實現我們所需要的功能。

比如 Create Table 就是使用 Iceberg 裡的 Catalog 來建立 Table 的,包含一些主鍵和 Schema。其他的操作,包括修改表的 Schema、寫入資料、提交快照等都是用純 Iceberg 的底層 API 來實現,沒有使用現有的 Flink Iceberg API 來做,這樣實現起來更加靈活。

14

在業務上,我們也會面臨很多複雜的業務場景,比如對同一欄位,我們會有很多種操作。比如需要支援 UDF;對欄位加過濾條件;欄位的對映;新增常量欄位;開啟欄位同步等等。所以我們在寫邏輯的時候,要考慮各種各樣複雜的條件。因為可能改了其中某一個功能進而就影響了其他功能。

四、生產實踐

15

我們系統上線後,目前已經服務於十幾個客戶,涉及到金融、能源等各個行業。支援的資料來源包括 MySQL、PostgreSQL、Oracle、SQL Server 等。資料規模方面,目前客戶用於同步的任務從幾個到幾十個庫不等,每秒同步數千條資料。

16

未來我們將在以下三方面進行提升:

  • 第一,做一些效能提升。做一些壓測,從各個角度提高系統的吞吐率和效能。
  • 第二,希望有更多引數配置。比如 Kafka Sink 的各種 Topic 配置、Iceberg 的分割槽配置等等。
  • 第三,希望有更多資料來源的支援。

點選檢視直播回放和演講 PPT


更多內容


活動推薦

阿里雲基於 Apache Flink 構建的企業級產品-實時計算Flink版現開啟活動:
99 元試用 實時計算Flink版(包年包月、10CU)即有機會獲得 Flink 獨家定製衛衣;另包 3 個月及以上還有 85 折優惠!
瞭解活動詳情:https://www.aliyun.com/product/bigdata/sc

image.png

相關文章