Flink的mysql CDC,好使不?
來源:安瑞哥是碼農
這些天,一直都折騰,看可以透過哪些手段來導mysql表的資料來源到各個目標資料庫中,為了摸清楚當下大概有哪些匯入方式,找出我認為最靠譜,最方便的,可謂煞費苦心。
前兩篇文章分別介紹了用dataX和CK外部表的玩法,透過基於我的應用場景實踐,詳細說明了這兩種資料匯入方式誰更靠譜。
那麼今天我們再來試試用Flink的CDC方案,用它來讀取mysql表,並把資料寫入到Doris,看整個過程有哪些坑,以及是否方便、靠譜。
其實從Flink官網來看,flink讀取mysql,還有一種jdbc的方式,只不過CDC這個概念被Flink宣傳的過於火熱,我們還是決定從它先開始。
0. 環境準備
既然要用Flink CDC這個功能(至於啥是CDC,網上遍地的資料,這裡暫不解釋),那高低得看一眼CDC的官網,看看我們在使用這玩意時,需要注意哪些內容。
Flink CDC官網地址(注意:它跟Flink官網是分開的):
這個官網最大的優點就是:可以不用梯子。
由於我當前開發環境Flink用的1.15版本,所以我第一個關心的問題就是,CDC作為一個額外的flink生態元件,我該選哪個版本?
不出所料,官網給出了詳細的版本相容性列表,因此我的1.15可以選擇的CDC版本有兩個:2.3跟2.4,既然是測試,這裡我選擇最新的2.4版本。
於是,我需要在我的IDE開發環境裡,引入對應的pom依賴:
關於本次測試需要的所有元件版本,如下表所示:
元件名稱 | 版本 |
Flink | 1.15.3 |
Flink CDC | 2.4.0 |
mysql | 5.5、8.0 |
Doris | 2.0 |
1. 建立mysql資料來源
本來我的叢集有臺機器已經部署了mysql,這個mysql是CentOS7官方預設源自帶的,版本為5.5,一開始想著直接就在這個mysql上做測試得了。
但是,我簡單寫了個demo跑一下發現,當前版本的mysql對於Flink的CDC來說,太低了,丟擲瞭如下的異常:
果然,想使用這玩意,也不是誰都配的,回頭再去確認了一下官方文件,需要mysql版本不能低於5.6(那低版本的資料想用Flink同步咋整呢?可能需要試試JDBC方案)。
於是就只能去部署一個更高版本的mysql例項,於是我就果斷下了個mysql8給部署起來了。
還是拿我之前的上網日誌資料,根據資料特點先建表:
CREATE TABLE `test02` (
`client_ip` varchar(50) NOT NULL,
`domain` varchar(100) NOT NULL,
`time` varchar(20) NOT NULL,
`target_ip` varchar(50) NOT NULL,
`rcode` int NOT NULL,
`query_type` int NOT NULL,
`authority_record` text,
`add_msg` text,
`dns_ip` varchar(50) DEFAULT NULL,
PRIMARY KEY (`client_ip`,`domain`,`time`,`target_ip`,`rcode`,`query_type`)
至於為什麼這個Primary key是這麼多的欄位組合,原因在於如果不這樣,容易造成因為主鍵重複導致資料寫入失敗。
隨後,我用load data語法,將一個本地資料檔案給匯入到該表中,在匯入中發現,即便上面這個Primary key的組合實際內容並沒有重複,但還是報了一些重複主鍵的錯誤,所以我懷疑,它這個組合鍵判斷是否重複的依據,是用的hash值。
為了快速達到測試效果,我只往這張表裡先寫入了1百多萬資料:
當然,還需要在該資料庫建立一個,外部可以連線到該表的一個外部使用者,並對其賦予相關的許可權,過於簡單的內容,這裡就不贅述了。
2. 匯入有哪些坑
為了讓程式碼更加簡潔方便,這裡用Flink的SQL API,不得不說,是真香。
package com.anryg.mysql_cdc
import java.time.Duration
import org.apache.flink.contrib.streaming.state.EmbeddedRocksDBStateBackend
import org.apache.flink.streaming.api.CheckpointingMode
import org.apache.flink.streaming.api.environment.CheckpointConfig.ExternalizedCheckpointCleanup
import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
import org.apache.flink.table.api.bridge.scala.StreamTableEnvironment
/**
* @DESC: 用Flink CDC讀取mysql寫Doris表
* @Auther: Anryg
* @Date: 2023/11/6 20:02
*/
object FlinkSQLFromMysql2Doris {
def main(args: Array[String]): Unit = {
val env = StreamExecutionEnvironment.getExecutionEnvironment
env.enableCheckpointing(10000L)/**這個必須加上,否則資料入不了庫*/
//env.setParallelism(args(0).toInt)
env.setStateBackend(new EmbeddedRocksDBStateBackend(true)) //新的設定state backend的方式
env.getCheckpointConfig.setCheckpointStorage("hdfs://192.168.211.106:8020/tmp/flink_checkpoint/FlinkSQLFromMysql2Doris")
env.getCheckpointConfig.setExternalizedCheckpointCleanup(ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION) //設定checkpoint記錄的保留策略
env.getCheckpointConfig.setAlignedCheckpointTimeout(Duration.ofMinutes(1L))
env.getCheckpointConfig.setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE)
val tableEnv = StreamTableEnvironment.create(env)
/**第一步:讀取mysql資料來源*/
tableEnv.executeSql(
"""
Create table data_from_mysql(
|`client_ip` STRING,
|`domain` STRING,
|`time` STRING,
|`target_ip` STRING,
|`rcode` int,
|`query_type` int,
|`authority_record` STRING,
|`add_msg` STRING,
|`dns_ip` STRING,
|PRIMARY KEY(`client_ip`,`domain`,`time`,`target_ip`,`rcode`,`query_type`) NOT ENFORCED
|)
|with(
|'connector' = 'mysql-cdc',
|'hostname' = '192.168.221.173',
| 'port' = '3306',
| 'username' = '****',
| 'password' = '****',
| 'database-name' = 'test',
| 'table-name' = 'test02' //確定文字資料來源的分隔符
|)
""".stripMargin)
/**第二步:建立Doris對映表*/
tableEnv.executeSql(
s"""
|CREATE TABLE data_from_flink01 (
|`client_ip` STRING,
|domain STRING,
|`time` STRING,
|target_ip STRING,
|rcode INT,
|query_type INT,
|authority_record STRING,
|add_msg STRING,
|dns_ip STRING
|)
| WITH (
| 'connector' = 'doris',
| 'fenodes' = '192.168.221.173:8030',
| 'table.identifier' = 'example_db.data_from_flink01',
| 'username' = '***',
| 'password' = '***',
| 'sink.label-prefix' = 'load01' //匯入標籤字首,每次要不一樣
|)
""".stripMargin)
/**第三步:資料寫入到Doris表中*/
tableEnv.executeSql(
"""
|INSERT INTO data_from_flink01
|select
|*
|from
|data_from_mysql
""".stripMargin)
}
}
程式碼邏輯雖然非常簡單,但是這玩意跑起來到底有沒有坑呢?
那必須有。
2.1 坑1
從我這些年的開發經驗來看,幾乎但凡你要往原本已經相對成熟的軟體專案中,再次引入新的pom依賴,那勢必會將原本寧靜祥和的依賴環境,掀起一場血雨腥風。
具體表現就是:新程式碼各種報錯,老程式碼也可能跑不利索。
先來看引入Flink CDC依賴之後,新程式碼拋的第一個異常:
Caused by: java.lang.ClassNotFoundException: com.google.common.collect.RangeSet
at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
at java.lang.ClassLoader.loadClass(ClassLoader.java:418)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:355)
at java.lang.ClassLoader.loadClass(ClassLoader.java:351)
說沒有找到 com.google.common.collect.RangeSet 這個類。
既然這樣,那就只能用老辦法來排除了。
先利用IDEA的類查詢功能,在當前專案瞅一眼,這個類,它到底有沒有?
不僅有,而且有3個jar包都包含這個類(1個 hive-exec 包,2個 guava 包),所以你是不是就此判斷,是因為這3個jar包衝突導致的這個報錯呢?
其實不然,要知道,這裡查詢出的結果,是當前軟體工程中,所有module的,也就是說,我們們的目標module(flink CDC所在的module),是不是真有這些jar包,還不一定,還得進一步確認。
果然一查發現,在flink cdc這個module下,hive-exec 這個包它就沒有,而 guava 的包也只有個低版本的。
那根據我的經驗判斷,應該是當前module缺少高版本的 guava 包導致的報錯(畢竟這個破包已經在我的多個專案中留下了案底,所以印象深刻),那既然是需要高版本,自然就想到要把這個低版本的guava給先排除掉。
於是,先排除低版本的guava,再引入一個高版本的guava。
幹掉低版本的,新增高版本的
(PS:大家可能會覺得,這看起來挺簡單的嘛,但如果要是你在開發時遇到了這個問題,你能做到這麼頭腦清醒,思維縝密嗎)
2.2 坑2
繼續啟動程式碼,又丟擲下面這個錯誤:
從錯誤提示來看,好像是某個資料格式不規範而導致的問題。
於是呢,為了確定這一點,我就新建了張表結構一樣的空表,再次啟動程式發現,還是報這個錯。
於是我開始懷疑是表欄位問題,就又開始逐個排查,從建一個欄位的表,逐漸增加到建包含所有欄位的表,居然,又都不報錯了。
然後我在新表中寫入資料,再次啟動程式,發現也正常了。
最後,我再次讓程式讀取最開始報錯的那張表,之前的錯誤居然消失了,要知道,我期間沒有做任何的修改,就離譜。
合著試一圈下來,是在玩我呢!
經過多次反覆的嘗試,我很懷疑這玩意就是flink CDC的bug,偶發,沒有特定規律。
3. CDC的寫入邏輯驗證
填完上面的坑之後,再次啟動程式,就能很順利將mysql中的資料全部匯入到Doris的目標表中了。
可以看到,已經把之前寫入到mysql中的所有資料,一條不落的都匯入過來了(對比上面mysql資料量的截圖),而且速度很快。
起初,我原本想著,既然是CDC,程式是不是隻會捕獲變化(新增)的資料才對,至於存量的歷史資料(匯入程式啟動之前就存在的資料),可能不會匯入。
但是經過驗證,Flink CDC它做到了,透過檢視程式執行的日誌可以發現,它之所以能做到這一點,跟它讀取的是binlog日誌密切相關。
CDC程式執行過程的日誌
為了進一步驗證這一點,我把mysql的binlog功能給關了試試,於是我在配置檔案中加入這個:
然後重啟mysql,結果發現很快,CDC程式就報錯了:
所以說明,mysql CDC能使用的前提是:必須要開啟binlog功能。
為了進一步驗證CDC程式能感知binlog的哪些操作,我又分別對mysql的源表做了update、insert和delete操作,我們分別都來看一下,這些操作會對Doris目的表有哪些影響?
先看update:
用update對mysql源表,其中的兩條記錄的某個欄位值做個修改。
然後再來看Doris目標表的記錄數有沒有變化:
可以看到,資料如期增加了2條(因為Doris這邊建立的是Duplicate模型表,所以是新增,不是覆蓋)。
再看insert:
接下來測試了新增記錄的情況,往mysql表寫入若干條記錄,程式能如期識別到並寫入到Doris目標表中(過於簡單,就不截圖了)。
最後看delete:
但是有意思的是,當我在源表中嘗試把之前修改過的2條記錄給刪除掉,結果你猜怎麼著,Doris目標表依然新增了那兩條我想要刪除的記錄。
也就是說:
對於Flink 的mysql CDC來說,除了對源表的select外,其他的如insert、update、delete 操作,都會觸發程式對目標表的新增資料。
所以保險起見,為了防止資料重複,對於承接CDC結果的目的表,最好使用去重表模型。
最後
從測試的結果來看,mysql的CDC能很好的滿足我們的資料匯入要求,只不過,並不是所有版本的mysql都有這個資格(需要5.6及以上版本)。
而且從實踐來看,對Flink mysql CDC的使用也並不沒有任何門檻,這裡面的一些坑,甚至隱藏的bug,都要求開發者有一定的問題排查和解決能力。
本來還想著基於同樣的場景,來對比一下spark的,但限於文章篇幅,今天就到這裡。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70027827/viewspace-2993610/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Clickhouse 的 mysql CDC,終於好使了MySql
- flink-cdc實時同步(oracle to mysql)OracleMySql
- Flink CDC 系列 - Flink MongoDB CDC 在 XTransfer 的生產實踐MongoDB
- FLINK CDC同步
- Flink MongoDB CDC 在 XTransfer 的生產實踐|Flink CDC 專題MongoDB
- [Flink/CDC/資料整合] 資料增量整合方案:Flink CDC
- FLINK CDC部署同步
- Flink CDC實戰
- Flink CDC3.0的介紹
- Flink CDC 在京東的探索與實踐
- Flink CDC 系列 - 實現 MySQL 資料實時寫入 Apache DorisMySqlApache
- flink-cdc3.0官方網站網站
- Flink CDC Meetup · Online,5.21 開講!
- Flink CDC 在易車的應用實踐
- 基於 Flink CDC 的實時同步系統
- Flink CDC 在大健雲倉的實踐
- Flink CDC 3.0 耍起來到底怎麼樣?
- 回顧|Flink CDC Meetup(附 PPT 下載)
- Flink CDC 系列 - 同步 MySQL 分庫分表,構建 Iceberg 實時資料湖MySql
- 基於 Flink CDC 的現代資料棧實踐
- Flink CDC MongoDB Connector 的實現原理和使用實踐MongoDB
- Flink CDC+Kafka 加速業務實時化Kafka
- Apache Flink CDC 批流融合技術原理分析Apache
- Flink CDC + Hudi 海量資料入湖在順豐的實踐
- Flink CDC 採集MySQL 初始化或者指定時間戳時,沒有采集到資料MySql時間戳
- zendesk/maxwell:MySQL的CDC資料更新捕獲者MySql
- 基於 Flink CDC 打造企業級實時資料整合方案
- Flink CDC 2.1 正式釋出,穩定性大幅提升,新增 Oracle,MongoDB 支援OracleMongoDB
- flink定時讀取mysqlMySql
- FLINK同時執行同步流和cdc流存在包衝突的問題--解決方案
- 如何實現對 Oracle 的實時資料捕獲和效能調優|Flink CDC 專題Oracle
- Flink cdc 2.2.0版本不能配置指定時間戳或者binlog位置採集時間戳
- db-cdc之mysql 深入瞭解並使用binlogMySql
- Maxwell 的 CDC,好用不?
- 三步玩轉:如何透過Flink OceanBase CDC聯結器快速查詢資料
- 【直播預告】優化器及 Flink CDC + OceanBase 全增量一體化資料整合方案優化
- CDC實戰:MySQL實時同步資料到Elasticsearch之陣列集合(array)如何處理【CDC實戰系列十二】MySqlElasticsearch陣列
- 讓你更好使用 Typescript 的11個技巧TypeScript