数据源连接器之MySQL CDC:Binlog原理与Flink实战
说到实时数据同步,MySQL CDC 是绕不开的核心技术。我个人做流式元数据同步项目时,第一关就是搞定 MySQL 的 Binlog。说白了,没有 Binlog,实时同步就是空谈。今天咱们就把它彻底讲透。
MySQL Binlog 到底是什么?
Binlog,全称 Binary Log,是 MySQL 的二进制日志。它记录了所有数据变更操作——INSERT、UPDATE、DELETE,甚至表结构变更。你想想看,数据库每发生一次写操作,Binlog 就会追加一条记录。
我刚开始接触时有个误区:以为 Binlog 和 Redo Log 是一回事。其实完全不同。Redo Log 是 InnoDB 存储引擎层面的,用于崩溃恢复;Binlog 是 MySQL Server 层面的,用于主从复制和数据恢复。咱们做 CDC,盯的就是 Binlog。
Binlog 的三种格式
| 格式 | 记录方式 | 特点 | CDC 适用性 |
|---|---|---|---|
| STATEMENT | 记录 SQL 语句 | 体积小,但上下文依赖强 | 不推荐,数据一致性难保证 |
| ROW | 记录每行变更前后数据 | 体积大,但精确可靠 | 强烈推荐,Flink CDC 首选 |
| MIXED | 混合模式 | MySQL 自动选择 | 可用,但不如 ROW 稳定 |
关键结论:做 Flink CDC 同步,Binlog 格式必须设置为 ROW。否则你拿到的数据可能不完整,甚至出现主键冲突。我在生产环境踩过这个坑,后来统一强制 ROW 格式。
Binlog 的核心机制
Binlog 文件是顺序写入的,每个文件写满后会滚动生成新文件。MySQL 内部维护一个 binlog position,CDC 客户端可以从任意位置开始消费。这就是 Flink 能做到断点续传的基础。
嗯,这里要注意:Binlog 不会永久保留。MySQL 有自动清理机制,通过 expire_logs_days 参数控制。我建议至少保留 3 天,给 CDC 任务留足容错时间。
Flink MySQL CDC Connector 配置与使用
Flink 官方提供的 mysql-cdc 连接器,底层封装了 Debezium 引擎。它监听 Binlog,把数据变更转成 Flink 能处理的 DataStream。说白了,你只需要配好连接信息,剩下的框架帮你搞定。
环境准备
先确认 MySQL 开启了 Binlog。登录 MySQL 执行:
SHOW VARIABLES LIKE 'log_bin';
SHOW VARIABLES LIKE 'binlog_format';
如果 log_bin 是 OFF,或者 binlog_format 不是 ROW,需要修改 MySQL 配置文件并重启。我曾经遇到一个客户,Binlog 没开,折腾了半天才发现——这种低级错误最耽误时间。
另外,CDC 读取 Binlog 需要一个有权限的账号:
GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'cdc_user'@'%' IDENTIFIED BY 'your_password';
FLUSH PRIVILEGES;
Maven 依赖
在 pom.xml 中添加:
<dependency>
<groupId>com.ververica</groupId>
<artifactId>flink-connector-mysql-cdc</artifactId>
<version>2.4.0</version>
</dependency>
注意版本要和 Flink 匹配。我用的是 Flink 1.17 + CDC 2.4,稳定运行了大半年。
核心配置参数
| 参数 | 说明 | 推荐值 |
|---|---|---|
| hostname | MySQL 主机地址 | 实际 IP 或域名 |
| port | MySQL 端口 | 3306 |
| database-name | 要监听的数据库 | 支持正则,如 db_.* |
| table-name | 要监听的表 | 支持正则,如 user_.* |
| server-id | CDC 客户端唯一 ID | 5400-5400 范围,避免冲突 |
| scan.startup.mode | 启动模式 | initial / latest-offset |
| debezium.snapshot.mode | 快照模式 | initial(全量+增量) |
我的经验:server-id 必须全局唯一。如果多个 CDC 任务用了相同 ID,MySQL 会认为连接冲突,导致其中一个任务断开。我曾经排查了整整一天才发现是 ID 重复的问题。
代码实战:单表同步
下面是一个完整的 Flink CDC 示例,监听 MySQL 的 orders 表,实时输出变更数据:
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import com.ververica.cdc.connectors.mysql.source.MySqlSource;
import com.ververica.cdc.debezium.JsonDebeziumDeserializationSchema;
public class MySQLCDCSingleTable {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(1);
env.enableCheckpointing(5000);
MySqlSource<String> mySqlSource = MySqlSource.<String>builder()
.hostname("192.168.1.100")
.port(3306)
.databaseList("ecommerce")
.tableList("ecommerce.orders")
.username("cdc_user")
.password("cdc_pass")
.serverId("5400")
.deserializer(new JsonDebeziumDeserializationSchema())
.build();
env.fromSource(mySqlSource, WatermarkStrategy.noWatermarks(), "MySQL CDC Source")
.print();
env.execute("MySQL CDC Single Table Sync");
}
}
启动后,对 orders 表做任何 INSERT、UPDATE、DELETE,控制台都会打印 JSON 格式的变更事件。你想想看,这就是实时同步的起点。
启动模式详解
scan.startup.mode 有两个常用值:
- initial:先做全量快照,再切换到增量 Binlog 监听。适合首次启动。
- latest-offset:直接从当前 Binlog 位置开始,不读历史数据。适合已有全量数据、只需增量同步的场景。
避坑指南:我曾经在生产环境用 latest-offset 启动,结果发现丢了一部分数据。原因是任务启动前有数据写入,但 Binlog 位置已经跳过了。后来我改成 initial,先做快照保证数据完整,再切增量。除非你确定历史数据不需要,否则别省这一步。
Binlog 到 Flink 的数据流转
为了让你更直观地理解整个过程,我画了一张流程图:
从图上你能看到,整个链路分三步:MySQL 产生 Binlog → Debezium 解析成事件 → Flink 消费并处理。其中 Checkpoint 机制保证了数据不丢不重。我个人觉得,理解这个流程比背配置参数更重要——参数可以查文档,但架构思维得自己建立。
多表监听与正则匹配
实际项目中,往往需要监听多张表甚至整个数据库。Flink CDC 支持正则表达式:
.databaseList("ecommerce")
.tableList("ecommerce.orders|ecommerce.users|ecommerce.products")
或者用通配符:
.databaseList("ecommerce")
.tableList("ecommerce.order_.*")
嗯,这里要注意:tableList 的格式是 数据库名.表名,中间用点号分隔。多个表用竖线 | 连接。我习惯把需要同步的表名写在一个配置文件里,启动时动态加载,方便运维调整。
常见问题与避坑
- Binlog 太大撑爆磁盘? 设置
expire_logs_days=3,配合max_binlog_size=500M控制单个文件大小。 - CDC 任务重启后重复消费? 开启 Flink Checkpoint,设置
env.enableCheckpointing(5000),保证 Exactly-Once 语义。 - MySQL 主从切换怎么办? CDC 连接的是主库地址。如果发生切换,需要手动更新连接配置并重启任务。目前 Flink CDC 还不支持自动跟随主从切换。
- DDL 变更怎么处理? Flink CDC 默认只捕获 DML(数据变更),不捕获 DDL(结构变更)。如果需要同步表结构变化,得额外配置 Debezium 的
include.schema.changes参数。
我曾经踩过的坑:有一次 MySQL 做了大表 ALTER TABLE,耗时 10 分钟。这期间 Binlog 暂停写入,CDC 任务一直等待,导致下游数据延迟飙升。后来我加了一个超时监控,如果 Binlog 超过 30 秒没有新事件,就触发告警。这种问题不常见,但遇到了就很头疼。
小结
MySQL CDC 的核心就三件事:Binlog 格式配成 ROW、Flink CDC 连接器配好参数、Checkpoint 保证容错。你只要把这三步走稳,实时同步的基础就搭好了。剩下的就是根据业务需求做数据转换和写入下游——那些咱们后面章节再聊。