多庫多表場景下使用 Amazon EMR CDC 實時入湖最佳實踐

亞馬遜雲開發者發表於2023-04-13

一、前言

CDC(Change Data Capture) 從廣義上講所有能夠捕獲變更資料的技術都可以稱為 CDC,但本篇文章中對 CDC 的定義限定為以非侵入的方式實時捕獲資料庫的變更資料。例如:透過解析 MySQL 資料庫的 Binlog 日誌捕獲變更資料,而不是透過 SQL Query 源表捕獲變更資料。 Hudi 作為最熱的資料湖技術框架之一, 用於構建具有增量資料處理管道的流式資料湖。其核心的能力包括物件儲存上資料行級別的快速更新和刪除,增量查詢(Incremental queries,Time Travel),小檔案管理和查詢最佳化(Clustering,Compactions,Built-in metadata),ACID 和併發寫支援。Hudi 不是一個 Server,它本身不儲存資料,也不是計算引擎,不提供計算能力。其資料儲存在 S3(也支援其它物件儲存和 HDFS),Hudi 來決定資料以什麼格式儲存在 S3(Parquet,Avro,…), 什麼方式組織資料能讓實時攝入的同時支援更新,刪除,ACID 等特性。Hudi 透過 Spark,Flink 計算引擎提供資料寫入, 計算能力,同時也提供與 OLAP 引擎整合的能力,使 OLAP 引擎能夠查詢 Hudi 表。從使用上看 Hudi 就是一個 JAR 包,啟動 Spark, Flink 作業的時候帶上這個 JAR 包即可。Amazon EMR 上的 Spark,Flink,Presto ,Trino 原生整合 Hudi, 且 EMR 的 Runtime 在 Spark,Presto 引擎上相比開源有2倍以上的效能提升。 在多庫多表的場景下(比如:百級別庫表),當我們需要將資料庫(mysql,postgres,sqlserver,oracle,mongodb 等)中的資料透過 CDC 的方式以分鐘級別(1minute+)延遲寫入 Hudi,並以增量查詢的方式構建數倉層次,對資料進行實時高效的查詢分析時。我們要解決三個問題,第一,如何使用統一的程式碼完成百級別庫表 CDC 資料並行寫入 Hudi,降低開發維護成本。第二,源端 Schema 變更如何同步到 Hudi 表。第三,使用 Hudi 增量查詢構建數倉層次比如 ODS->DWD->DWS (各層均是 Hudi 表),DWS 層的增量聚合如何實現。本篇文章推薦的方案是: 使用 Flink CDC DataStream API (非 SQL)先將 CDC 資料寫入 Kafka,而不是直接透過 Flink SQL 寫入到 Hudi 表,主要原因如下,第一,在多庫表且 Schema 不同的場景下,使用 SQL 的方式會在源端建立多個 CDC 同步執行緒,對源端造成壓力,影響同步效能。第二,沒有 MSK 做 CDC 資料上下游的解耦和資料緩衝層,下游的多端消費和資料回溯比較困難。CDC 資料寫入到 MSK 後,推薦使用 Spark Structured Streaming DataFrame API 或者 Flink StatementSet 封裝多庫表的寫入邏輯,但如果需要源端 Schema 變更自動同步到 Hudi 表,使用 Spark Structured Streaming DataFrame API 實現更為簡單,使用 Flink 則需要基於 HoodieFlinkStreamer 做額外的開發。Hudi 增量 ETL 在 DWS 層需要資料聚合的場景的下,可以透過 Flink Streaming Read 將 Hudi 作為一個無界流,透過 Flink 計算引擎完成資料實時聚合計算寫入到 Hudi 表。

亞馬遜雲科技開發者社群為開發者們提供全球的開發技術資源。這裡有技術文件、開發案例、技術專欄、培訓影片、活動與競賽等。幫助中國開發者對接世界最前沿技術,觀點,和專案,並將中國優秀開發者或技術推薦給全球雲社群。如果你還沒有關注/收藏,看到這裡請一定不要匆匆劃過,點這裡讓它成為你的技術寶庫!

二、架構設計與解析

2.1 CDC 資料實時寫入 MSK

圖中標號1,2是將資料庫中的資料透過 CDC 方式實時傳送到 MSK (Amazon 託管的 Kafka 服務)。flink-cdc-connectors 是當前比較流行的 CDC 開源工具。它內嵌debezium 引擎,支援多種資料來源,對於 MySQL 支援 Batch 階段(全量同步階段)並行,無鎖,Checkpoint (可以從失敗位置恢復,無需重新讀取,對大表友好)。支援 Flink SQL API 和 DataStream API,這裡需要注意的是如果使用 SQL API 對於庫中的每張表都會單獨建立一個連結,獨立的執行緒去執行 binlog dump。如果需要同步的表比較多,會對源端產生較大的壓力。在需要整庫同步表非常多的場景下,應該使用 DataStream API 寫程式碼的方式只建一個 binlog dump 同步所有需要的庫表。另一種場景是如果只同步分庫分表的資料,比如 user 表做了分庫,分表,其表 Schema 都是一樣的,Flink CDC 的 SQL API 支援正則匹配多個庫表,這時使用 SQL API 同步依然只會建立一個 binlog dump 執行緒。需要說明的是透過 Flink CDC 可以直接將資料 Sink 到 Hudi, 中間無需 MSK,但考慮到上下游的解耦,資料的回溯,多業務端消費,多表管理維護,依然建議 CDC 資料先到 MSK,下游再從 MSK 接資料寫入 Hudi。

2.2 CDC 工具對比

圖中標號3,除了 flink-cdc-connectors 之外,DMS (Amazon Database Migration Services) 是 Amazon 託管的資料遷移服務,提供多種資料來源 (mysql,oracle,sqlserver,postgres,mongodb,documentdb 等)的 CDC 支援,支援視覺化的 CDC 任務配置,執行,管理,監控。因此可以選擇 DMS 作為 CDC 的解析工具,DMS 支援將 MSK 或者自建 Kafka 作為資料投遞的目標,所以 CDC 實時同步到 MSK 透過 DMS 可以快速視覺化配置管理。當然除了 DMS 之外還有很多開源的 CDC 工具,也可以完成 CDC 的同步工作,但需要在 EC2 上搭建相關服務。下圖列出了 CDC 工具的對比項,供大家參考

2.3 Spark Structured Streaming 多庫表並行寫 Hudi 及 Schema 變更
圖中標號4,CDC 資料到了 MSK 之後,可以透過 Spark/Flink 計算引擎消費資料寫入到 Hudi 表,我們把這一層我們稱之為 ODS 層。無論 Spark 還是 Flink 都可以做到資料 ODS 層的資料落地,使用哪一個我們需要綜合考量,這裡闡述一些相對重要的點。首先對於 Spark 引擎,我們一定是使用 Spark Structured Streaming 消費 MSK 寫入 Hudi,由於可以使用 DataFrame API 寫 Hudi, 因此在 Spark 中可以方便的實現消費 CDC Topic 並根據其每條資料中的元資訊欄位(資料庫名稱,表名稱等)在單作業內分流寫入不同的 Hudi 表,封裝多表並行寫入邏輯,一個 Job 即可實現整庫多表同步的邏輯。樣例程式碼截圖如下,完整程式碼點選 Github 獲取

我們知道 CDC 資料中是帶著 I(insert)、U(update)、D(delete) 資訊的, 不同的 CDC 工具資料格式不同,但要表達的含義是一致的。使用 Spark 寫入 Hudi 我們主要關注 U、D 資訊,資料帶著U資訊表示該條資料是一個更新操作,對於 Hudi 而言只要設定源表的主鍵為 Hudi 的 recordKey,同時根據需求場景設定 precombineKey 即可。這裡對 precombineKey 做一個說明,它表示的是當資料需要更新時(recordKey 相同), 預設選擇兩條資料中 precombineKey 的大保留在 Hudi 中。其實 Hudi 有非常靈活的 Payload 機制,透過引數 hoodie.datasource.write.payload.class 可以選擇不同的 Payload 實現,比如 Partial Update (部分欄位更新)的Payload實現 OverwriteNonDefaultsWithLatestAvroPayload,也可以自定義 Payload 實現類,它核心要做的就是如何根據 precombineKey 指定的欄位更新資料。所以對於 CDC 資料 Sink Hudi 而言,我們需要保證上游的訊息順序,只要我們表中有能判斷哪條資料是最新的資料的欄位即可,那這個欄位在 MySQL 中往往我們設計成資料更新時間 modify_time timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP 。如果沒有類似欄位,建議定義設計規範加上這個欄位,否則就必須保證資料有序(這會給架構設計和效能帶來更多的阻力),不然資料在 Hudi 中 Updata 的結果可能就是錯的。對於帶著 D 資訊的資料,它表示這條資料在源端被刪除,Hudi 是提供刪除能力的,其中一種方式是當一條資料中包含 _hoodie_is_deleted 欄位,且值為 true 是,Hudi 會自動刪除此條資料,這在 Spark Structured Streaming 程式碼中很容易實現,只需在 map 操作實現新增一個欄位且當資料中包含 D 資訊設定欄位值為 true 即可。

2.4 Flink StatementSet 多庫表 CDC 並行寫 Hudi

對於使用 Flink 引擎消費 MSK 中的 CDC 資料落地到 ODS 層 Hudi 表,如果想要在一個 JOB 實現整庫多張表的同步,Flink StatementSet 來實現透過一個 Kafka 的 CDC Source 表,根據元資訊選擇庫表 Sink 到 Hudi 中。但這裡需要注意的是由於 Flink 和 Hudi 整合,是以 SQL 方式先建立表,再執行 Insert 語句寫入到該表中的,如果需要同步的表有上百之多,封裝一個自動化的邏輯能夠減輕我們的工作,你會發現 SQL 方式寫入 Hudi 雖然對於單表寫入使用上很方便,不用程式設計只需要寫 SQL 即可,但也帶來了一些限制,由於寫入 Hudi 時是透過 SQL 先建表,Schema 在建表時已將定義,如果源端 Schema 變更,透過 SQL 方式是很難實現下游 Hudi 表 Schema 的自動變更的。雖然在 Hudi 的官網並未提供 Flink DataStream API 寫入 Hudi 的例子,但 Flink 寫入 Hudi 是可以透過 HoodieFlinkStreamer 以 DataStream API 的方式實現,在 Hudi 原始碼中可以找到。因此如果想要更加靈活簡單的實現多表的同步,以及 Schema 的自動變更,需要自行參照 HoodieFlinkStreamer 程式碼以 DataStream API 的方式寫 Hudi。對於 I,U,D 資訊,Flink 的 debezium ,maxwell,canal format 會直接將訊息解析為 Flink 的 changelog 流,換句話說就是 Flink 會將 I,U,D 操作直接解析成 Flink 內部的資料結構 RowData,直接 Sink 到 Hudi 表即可,我們同樣需要在 SQL 中設定 recordKey,precombineKey,也可以設定 Payload class 的不同實現類。

2.5 Flink Streaming Read 模式讀 Hudi 實現 ODS 層聚合

圖中標號5,資料透過 Spark/Flink 落地到 ODS 層後,我們可能需要構建 DWD 和 DWS 層對資料做進一步的加工處理,(DWD 和 DWS 並非必須的,根據你的場景而定,你可以直接讓 OLAP 引擎查詢 ODS 層的 Hudi 表)我們希望能夠使用到 Hudi 的增量查詢能力,只查詢變更的資料來做後續 DWD 和 DWS 的 ETL,這樣能夠加速構建同時減少資源消耗。對於 Spark 引擎,在 DWD 層如果僅僅是對資料做 map,fliter 等相關型別操作,是可以使用增量查詢的,但如果 DWD 層的構建有 Join 操作,是無法透過增量查詢實現的,只能全表(或者分割槽)掃描。DWS 層的構建如果聚合型別的操作沒有去重,視窗型別的操作,只是 SUM, AVG,MIN, MAX 等型別的操作,可以透過增量查詢之後和目標表做 Merge 實現,反之,只能全表(或者分割槽)掃描。 對於 Flink 引擎來構建 DWD 和 DWS, 由於 Flink 支援 Hudi 表的 streaming read, 在 SQL 設定 read.streaming.enabled= true,changelog.enabled=true 等相關流式讀取的引數即可。設定後 Flink 把 Hudi 表當做了一個無界的 changelog 流表,無論怎樣做 ETL 都是支援的, Flink 會自身儲存狀態資訊,整個 ETL 的鏈路是流式的。

2.6 OLAP 引擎查詢 Hudi 表

圖中標號6, EMR Hive/Presto/Trino 都可以查詢 Hudi 表,但需要注意的是不同引擎對於查詢的支援是不同的,參見官網,這些引擎對於 Hudi 表只能查詢,不能寫入。 關於 Schema 的自動變更,首先 Hudi 自身是支援 Schema Evolution,我們想要做到源端 Schema 變更自動同步到 Hudi 表,透過上文的描述,可以知道如果使用 Spark 引擎,可以透過 DataFrame API 運算元據,透過 from_json 動態生成 DataFrame,因此可以較為方便的實現自動新增列。如果使用 Flink 引擎上文已經說明想要自動實現 Schema 的變更,透過 HoodieFlinkStreamer 以DataStream API 的方式實現 Hudi 寫入的同時融入 Schema 變更的邏輯。

三、EMR CDC 整庫同步 Demo

接下的 Demo 操作中會選擇 RDS MySQL 作為資料來源,Flink CDC DataStream API 同步庫中的所有表到 Kafka,使用 Spark 引擎消費 Kafka 中 binlog 資料實現多表寫入 ODS 層 Hudi,使用 Flink 引擎以 streaming read 的模式做 DWD 和 DWS 層的 Hudi 表構建。

3.1 環境資訊

EMR 6.6.0 
Hudi 0.10.0 
Spark 3.2.0 
Flink 1.14.2  
Presto 0.267
MySQL 5.7.34

3.2 建立源表

在 MySQL 中建立 test_db 庫及 user,product,user_order 三張表,插入樣例資料,後續 CDC 先載入表中已有的資料,之後源新增新資料並修改表結構新增新欄位,驗證 Schema 變更自動同步到 Hudi 表。

-- create databases
create database if not exists test_db default character set utf8mb4 collate utf8mb4_general_ci;
use test_db;

-- create  user table
drop table if exists user;
create table if not exists user
(
    id           int auto_increment primary key,
    name         varchar(155)                        null,
    device_model varchar(155)                        null,
    email        varchar(50)                         null,
    phone        varchar(50)                         null,
    create_time  timestamp default CURRENT_TIMESTAMP not null,
    modify_time  timestamp default CURRENT_TIMESTAMP null on update CURRENT_TIMESTAMP
)charset = utf8mb4;

-- insert data
insert into user(name,device_model,email,phone) values
('customer-01','dm-01','abc01@email.com','188776xxxxx'),
('customer-02','dm-02','abc02@email.com','166776xxxxx');

-- create product table
drop table if exists product;
create table if not exists product
(
    pid          int not null primary key,
    pname        varchar(155)                        null,
    pprice       decimal(10,2)                           ,
    create_time  timestamp default CURRENT_TIMESTAMP not null,
    modify_time  timestamp default CURRENT_TIMESTAMP null on update CURRENT_TIMESTAMP
)charset = utf8mb4;

-- insert data
insert into product(pid,pname,pprice) values
('1','prodcut-001',125.12),
('2','prodcut-002',225.31);

-- create order table
drop table if exists user_order;
create table if not exists user_order
(
    id           int auto_increment primary key,
    oid          varchar(155)                        not null,
    uid          int                                         ,
    pid          int                                         ,
    onum         int                                         ,
    create_time  timestamp default CURRENT_TIMESTAMP not null,
    modify_time  timestamp default CURRENT_TIMESTAMP null on update CURRENT_TIMESTAMP
)charset = utf8mb4;

-- insert data
insert into user_order(oid,uid,pid,onum) values 
('o10001',1,1,100),
('o10002',1,2,30),
('o10001',2,1,22),
('o10002',2,2,16);

-- select data
select * from user;
select * from product;
select * from user_order;
複製程式碼

3.3 Flink CDC 傳送資料到 Kafka

使用 DataStream API 編寫 CDC 同步程式。樣例程式碼 Github

# 建立topic
kafka-topics.sh --create --zookeeper ${zk}  --replication-factor 2 --partitions 8  --topic cdc_topic
# 下載程式碼,編譯打包
mvn clean package  -Dscope.type=provided  -DskipTests
# 也可以使用已經打好的包,進入EMR主節點,執行命令
wget https://dxs9dnjebzm6y.cloudfront.net/tmp/emr-flink-cdc-1.0-SNAPSHOT.jar
# disalbe check-leaked-classloader
sudo sed -i -e '$a\classloader.check-leaked-classloader: false' /etc/flink/conf/flink-conf.yaml
# 啟動flink cdc 傳送資料到Kafka
sudo flink run -m yarn-cluster \
-yjm 1024 -ytm 2048 -d \
-ys 4 -p 8 \
-c  com.aws.analytics.MySQLCDC  \
/home/hadoop/emr-flink-cdc-1.0-SNAPSHOT.jar \
-b xxxxx.amazonaws.com:9092 \
-t cdc_topic_001 \
-c s3://xxxxx/flink/checkpoint/ \
-l 30 -h xxxxx.rds.amazonaws.com:3306 -u admin \
-P admin123456 \
-d test_db -T test_db.* \
-p 4 \
-e 5400-5408
# 相關的引數說明如下
MySQLCDC 1.0
Usage: MySQLCDC [options]

  -c, --checkpointDir <value>
                           checkpoint dir
  -l, --checkpointInterval <value>
                           checkpoint interval: default 60 seconds
  -b, --brokerList <value>
                           kafka broker list,sep comma
  -t, --sinkTopic <value>  kafka topic
  -h, --host <value>       mysql hostname, eg. localhost:3306
  -u, --username <value>   mysql username
  -P, --pwd <value>        mysql password
  -d, --dbList <value>     cdc database list: db1,db2,..,dbn
  -T, --tbList <value>     cdc table list: db1.*,db2.*,db3.tb*...,dbn.*
  -p, --parallel <value>   cdc source parallel
  -s, --position <value>   cdc start position: initial or latest,default: initial
  -e, --serverId <value>   cdc server id
  
# 消費Kafka topic 觀察資料
./kafka_2.12-2.6.2/bin/kafka-console-consumer.sh --bootstrap-server $brok --topic cdc_topic_001 --from-beginning |jq .

3.4 Spark 消費 CDC 資料整庫同步

# 整庫同步樣例程式碼  https://github.com/yhyyz/emr-hudi-example/blob/main/src/main/scala/com/aws/analytics/Debezium2Hudi.scala

# 下載程式碼,編譯打包
mvn clean package  -Dscope.type=provided  -DskipTests
# 也可以使用已經打好的包,進入EMR主節點,執行命令
wget https://dxs9dnjebzm6y.cloudfront.net/tmp/emr-hudi-example-1.0-SNAPSHOT-jar-with-dependencies.jar 

# 執行如下命令提交作業,命令中設定-s hms,hudi表同步到Glue Catalog
spark-submit  --master yarn \
--deploy-mode client \
--driver-memory 1g \
--executor-memory 1g \
--executor-cores 2 \
--num-executors  2 \
--conf "spark.dynamicAllocation.enabled=false" \
--conf "spark.serializer=org.apache.spark.serializer.KryoSerializer" \
--conf "spark.sql.hive.convertMetastoreParquet=false" \
--jars  /usr/lib/hudi/hudi-spark-bundle.jar,/usr/lib/spark/external/lib/spark-avro.jar \
--class com.aws.analytics.Debezium2Hudi /home/hadoop/emr-hudi-example-1.0-SNAPSHOT-jar-with-dependencies.jar \
-e prod -b xxxxx.amazonaws.com:9092 \
-t cdc_topic_001 -p emr-cdc-group-02 -s true \
-o earliest \
-i 60 -y cow -p 10 \
-c s3://xxxxx/spark-checkpoint/emr-hudi-cdc-005/ \
-g s3://xxxxx/emr-hudi-cdc-005/ \
-r jdbc:hive2://localhost:10000  \
-n hadoop -w upsert  \
-s hms \
--concurrent false \
-m "{\"tableInfo\":[{\"database\":\"test_db\",\"table\":\"user\",\"recordKey\":\"id\",\"precombineKey\":\"modify_time\",\"partitionTimeColumn\":\"create_time\",\"hudiPartitionField\":\"year_month\"},
{\"database\":\"test_db\",\"table\":\"user_order\",\"recordKey\":\"id\",\"precombineKey\":\"modify_time\",\"partitionTimeColumn\":\"create_time\",\"hudiPartitionField\":\"year_month\"},{\"database\":\"test_db\",\"table\":\"product\",\"recordKey\":\"pid\",\"precombineKey\":\"modify_time\",\"partitionTimeColumn\":\"create_time\",\"hudiPartitionField\":\"year_month\"}]}"

# 相關引數說明如下:
Debezium2Hudi 1.0
Usage: spark ss Debezium2Hudi [options]

  -e, --env <value>        env: dev or prod
  -b, --brokerList <value>
                           kafka broker list,sep comma
  -t, --sourceTopic <value>
                           kafka topic
  -p, --consumeGroup <value>
                           kafka consumer group
  -s, --syncHive <value>   whether sync hive,default:false
  -o, --startPos <value>   kafka start pos latest or earliest,default latest
  -m, --tableInfoJson <value>
                           table info json str
  -i, --trigger <value>    default 300 second,streaming trigger interval
  -c, --checkpointDir <value>
                           hdfs dir which used to save checkpoint
  -g, --hudiEventBasePath <value>
                           hudi event table hdfs base path
  -y, --tableType <value>  hudi table type MOR or COW. default COW
  -t, --morCompact <value>
                           mor inline compact,default:true
  -m, --inlineMax <value>  inline max compact,default:20
  -r, --syncJDBCUrl <value>
                           hive server2 jdbc, eg. jdbc:hive2://localhost:10000
  -n, --syncJDBCUsername <value>
                           hive server2 jdbc username, default: hive
  -p, --partitionNum <value>
                           repartition num,default 16
  -w, --hudiWriteOperation <value>
                           hudi write operation,default insert
  -u, --concurrent <value>
                           write multiple hudi table concurrent,default false
  -s, --syncMode <value>   sync mode,default jdbc, glue catalog set dms
  -z, --syncMetastore <value>
                           hive metastore uri,default thrift://localhost:9083
                           
# 下圖可以看到表已經同步到Glue Catalog ,資料已經寫入到S3

-- 向MySQL的user表中新增一列,並插入一條新資料, 查詢hudi表,可以看到新列和資料已經自動同步到user表,注意以下SQL在MySQL端執行
alter table user add column age int
insert into user(name,device_model,email,phone,age) values
('customer-03','dm-03','abc03@email.com','199776xxxxx',18);

3.5 Flink Streaming Read 實時聚合

# 注意最後一個引數,-t 是把/etc/hive/conf/hive-site.xml 加入到classpath,這樣hudi執行表同步到Glue是就可以加入載入到這個配置,配置中的關鍵是 hive.metastore.client.factory.class = com.amazonaws.glue.catalog.metastore.AWSGlueDataCatalogHiveClientFactory,這樣就可以載入用到Glue的Catalog實現. 如果EMR叢集啟動時就選擇了Glue Metastore,該檔案中/etc/hive/conf/hive-site.xml 已經配置了AWSGlueDataCatalogHiveClientFactory. 如果啟動EMR沒有選擇Glue Metastore,還需要同步資料到Glue,需要手動加上。

# 注意替換為你的S3 Bucket
checkpoints=s3://xxxxx/flink/checkpoints/datagen/

flink-yarn-session -jm 1024 -tm 4096 -s 2  \
-D state.backend=rocksdb \
-D state.checkpoint-storage=filesystem \
-D state.checkpoints.dir=${checkpoints} \
-D execution.checkpointing.interval=5000 \
-D state.checkpoints.num-retained=5 \
-D execution.checkpointing.mode=EXACTLY_ONCE \
-D execution.checkpointing.externalized-checkpoint-retention=RETAIN_ON_CANCELLATION \
-D state.backend.incremental=true \
-D execution.checkpointing.max-concurrent-checkpoints=1 \
-D rest.flamegraph.enabled=true \
-d \
-t /etc/hive/conf/hive-site.xml 

# 啟動Flink sql client
/usr/lib/flink/bin/sql-client.sh embedded -j /usr/lib/hudi/hudi-flink-bundle.jar shell
-- user表,開啟streaming read, changelog.enalbe=true
set sql-client.execution.result-mode=tableau;

CREATE TABLE `user`(
    id string,
    name STRING,
    device_model STRING,
    email STRING,
    phone STRING,
    age string,
    create_time STRING,
    modify_time STRING,
    year_month STRING
)
PARTITIONED BY (`year_month`)
WITH (
  'connector' = 'hudi',
  'path' = 's3://xxxxx/emr-hudi-cdc-005/test_db/user/',
  'hoodie.datasource.write.recordkey.field' = 'id',
  'table.type' = 'COPY_ON_WRITE',
  'index.bootstrap.enabled' = 'true',
  'read.streaming.enabled' = 'true',
  'read.start-commit' = '20220607014223',
  'changelog.enabled' = 'false',
  'read.streaming.check-interval' = '1'
);

# 實時查詢資料
select * from `user`;

# 在MySQL中修改user表中id=3的name為new-customer-03,注意以下SQL在MySQL端執行
update  user set name="new-customer-03" where id=3;

# 在Flink 端可以可以看到資料變更

-- Flink聚合操作Sink到Hudi表

-- batch
CREATE TABLE  user_agg(
num BIGINT,
device_model STRING
)WITH(
  'connector' = 'hudi',
  'path' = 's3://xxxxx/emr-cdc-hudi/user_agg/',
  'table.type' = 'COPY_ON_WRITE',  
  'write.precombine.field' = 'device_model',
  'write.operation' = 'upsert',
  'hoodie.datasource.write.recordkey.field' = 'device_model',
  'hive_sync.database' = 'dws',
  'hive_sync.enable' = 'true',
  'hive_sync.table' = 'user_agg',
  'hive_sync.mode' = 'HMS',
  'hive_sync.use_jdbc' = 'false',
  'hive_sync.username' = 'hadoop'
);

insert into user_agg select count(1) as num, device_model from `user` group by device_model;

# 動態引數開啟,對user_agg表進行streaming讀取,檢視實時變化結果
set table.dynamic-table-options.enabled=true;
select *  from user_agg/*+ OPTIONS('read.streaming.enabled'='true','read.start-commit' = '20220607014223')*/ 

# 可以在MySQL源端多新增幾條資料,檢視資料結果,注意以下SQL在MySQL端執行
insert into user(name,device_model,email,phone,age) values ('customer-03','dm-03','abc03@email.com','199776xxxxx',18);

四、總結

本篇文章講解了如何透過 EMR 實現 CDC 資料入湖及 Schema 的自動變更。透過 Flink CDC DataStream API 先將整庫資料傳送到 MSK,這時 CDC 在源端只有一個 binlog dump 執行緒,降低對源端的壓力。使用 Spark Structured Streaming 動態解析資料寫入到 Hudi 表來實現 Shema 的自動變更,實現單個 Job 管理多表 Sink, 多表情況下降低開發維護成本,可以並行或者序列寫多張 Hudi 表,後設資料同步 Glue Catalog。使用 Flink Hudi 的 Streaming Read 模式實現實時資料 ETL,滿足 DWD 和 DWS 層的實時 Join 和聚合的需求。Amazon EMR 環境中原生整合 Hudi, 使用 Amazon EMR 輕鬆構建了整庫同步的 Demo。

本篇作者

潘超
亞馬遜雲科技資料分析解決方案架構師。負責客戶大資料解決方案的諮詢與架構設計,在開源大資料方面擁有豐富的經驗。工作之外喜歡爬山。

文章連結:https://dev.amazoncloud.cn/column/article/6309e29be0f88a79bcf...

相關文章