没有Kafka怎么办?Flink SQL 创建 mysql-cdc 作业

前言
在大数据与实时计算的浪潮中,如何高效、低延迟地捕获和处理业务数据库的变更日志,已成为构建流式数据管道的关键。MySQL-CDC(Change Data Capture)
技术正是为此而生,它能够实时监听MySQL
数据库的二进制日志(Binlog
),精准捕捉数据的插入、更新和删除等变更事件,并将这些事件作为连续不断的流推送到下游处理系统,为实时数仓、数据同步和流式分析提供了源头活水。
与传统依赖定时查询的JDBC
连接方式相比,MySQL-CDC
带来了一场范式革命。JDBC
作业通常以固定的时间间隔轮询拉取数据,无法感知中间的变化,既存在显著的延迟,也可能因频繁查询而对源库造成压力。而CDC
则是基于事件的、真正的流式处理,它毫秒级地响应变化,保证了数据的端到端实时性,并且减少了对源库的侵入性。
因此,借助Flink SQL
创建MySQL-CDC
作业,开发者可以轻松地将数据库变更流与Kafka
、Hudi
、Iceberg
等数据湖仓无缝集成,广泛应用于实时数仓ETL
、物化视图更新、微服务间缓存同步、审计以及在线机器学习特征更新等核心场景,是实现"流批一体"架构不可或缺的利器。
添加依赖
下载后将flink-sql-connector-mysql-cdc-2.4.0.jar
包拷贝到 $FLINK_HOME/lib
和 $DINKY_HOME/extends/flink1.17/
目录下
bash
# mysql-cdc连接器,注意版本兼容性
wget https://repo1.maven.org/maven2/com/ververica/flink-sql-connector-mysql-cdc/2.4.0/flink-sql-connector-mysql-cdc-2.4.0.jar
Flink SQL代码
配置为mysql-cdc
连接器的源表
sql
CREATE TABLE users
(
`id` BIGINT NOT NULL COMMENT '主键id',
`user_name` STRING COMMENT '姓名',
`sex` STRING COMMENT '性别',
`age` BIGINT COMMENT '年龄',
PRIMARY KEY (`id`) NOT ENFORCED
) WITH (
'connector' = 'mysql-cdc',
'hostname' = 'xxx.xxx.xxx.xxx',
'port' = 'xxxx',
'username' = 'xxxx',
'password' = 'xxxx',
'database-name' = 'xxxx',
'table-name' = 'users',
'server-time-zone' = 'Asia/Shanghai',
'scan.startup.mode' = 'initial', -- 先做全量快照,然后从binlog最新位置开始读取(默认)
-- 'scan.startup.mode' = 'earliest-offset', -- 直接跳过快照,从binlog最早位置开始读取。
-- 'scan.startup.mode' = 'latest-offset',
'debezium.skipped.operations' = 'd,t' -- 跳过删除/truncate操作
);
'scan.startup.mode' = 'initial'
第一次会做全量读取,后续做增量读取;'debezium.skipped.operations' = 'd,t'
表示源表即使做了delete
或者truncate
操作,目标表也不会同步执行,这是为了防止源表删库跑路牵连到了目标表;
配置为jdbc
连接器的目标表
sql
CREATE TABLE users2
(
`id` BIGINT NOT NULL COMMENT '主键id',
`user_name` STRING COMMENT '姓名',
`sex` STRING COMMENT '性别',
`age` BIGINT COMMENT '年龄',
PRIMARY KEY (`id`) NOT ENFORCED
) WITH (
'connector' = 'jdbc',
'table-name' = 'users2',
'url' = 'jdbc:mysql://x.x.x.x:3306/xxxx?useSSL=false&serverTimezone=UTC',
'username' = 'xxxx',
'password' = 'xxxx',
'sink.buffer-flush.max-rows' = '100', -- 根据性能调整,小批量写入
'sink.buffer-flush.interval' = '5s', -- 根据性能调整,控制写入延迟
-- 'sink.parallelism' = '4', -- 增加写入并发
'sink.max-retries' = '3' -- 写入失败重试
);
最后sink
语句
sql
insert into user2
select * from users
多个sink表
如果想在一个作业里将源表的数据,链式传输给多个表,例如上文中从users
表同步数据到users2
表和users3
表,就要再增加一个users3
表的jdbc
映射,以及insert
语句。
sql
CREATE TABLE users3
(
`id` BIGINT NOT NULL COMMENT '主键id',
`user_name` STRING COMMENT '姓名',
`sex` STRING COMMENT '性别',
`age` BIGINT COMMENT '年龄',
PRIMARY KEY (`id`) NOT ENFORCED
) WITH (
'connector' = 'jdbc',
'table-name' = 'users3',
'url' = 'jdbc:mysql://x.x.x.x:3306/xxxx?useSSL=false&serverTimezone=UTC',
'username' = 'xxxx',
'password' = 'xxxx',
'sink.buffer-flush.max-rows' = '100', -- 根据性能调整,小批量写入
'sink.buffer-flush.interval' = '5s', -- 根据性能调整,控制写入延迟
-- 'sink.parallelism' = '4', -- 增加写入并发
'sink.max-retries' = '3' -- 写入失败重试
);
insert into users3
select * from users;
注意这里的源表还是users
,而不是users2
,users2
映射的是jdbc连接器,所以如果源表使用users2
的话,users3
是不会读取到更新记录的。
由于我这里写的场景比较简单,所以insert into users3
的代码很少,如果users
表到users2
和users3
表的结构差异比较大,中间做了很多转换,导致insert sql
逻辑复杂,那么跳过users2
而直接从users
表insert into users3
时可读性就会很差。解决办法如下:
sql
CREATE TABLE users2_cdc
(
`id` BIGINT NOT NULL COMMENT '主键id',
`user_name` STRING COMMENT '姓名',
`sex` STRING COMMENT '性别',
`age` BIGINT COMMENT '年龄',
PRIMARY KEY (`id`) NOT ENFORCED
) WITH (
'connector' = 'mysql-cdc',
'hostname' = 'xxx.xxx.xxx.xxx',
'port' = 'xxxx',
'username' = 'xxxx',
'password' = 'xxxx',
'database-name' = 'xxxx',
'table-name' = 'users2',
'server-time-zone' = 'Asia/Shanghai',
'scan.startup.mode' = 'initial', -- 先做全量快照,然后从binlog最新位置开始读取(默认)
-- 'scan.startup.mode' = 'earliest-offset', -- 直接跳过快照,从binlog最早位置开始读取。
-- 'scan.startup.mode' = 'latest-offset',
'debezium.skipped.operations' = 'd,t' -- 跳过删除/truncate操作
);
insert into users3
select * from users2_cdc;
users2_cdc
这个表要使用mysql-cdc
连接器,虽然它也是users2
表,但是和前文中创建的使用jdbc连接器的users2
表要分别创建。以上的两条链路也可以分别写在不同的Flink SQL
作业里。
