1. 项目背景与整体思路
1.1 为什么需要从 MySQL 同步到达梦
在实际业务里,MySQL 经常作为业务库跑了很长时间,积累了非常多的数据。随着国产化改造推进,很多系统需要把数据迁移到达梦数据库。迁移这个动作本身不难,真正让人头疼的是增量同步——业务不能停,MySQL 那边每秒钟都在写入,怎么把写入的每一条变更实时或准实时地同步到达梦,这是我这个项目要解决的核心问题。
项目标题里的 "FlinkCDC_达梦JDBC_MySQL同步到达梦" 其实已经把技术选型说得非常清楚了:源头是 MySQL,通过 Flink CDC 捕获 binlog 里的变更,然后借助 JDBC 连接器批量写入达梦数据库。这个方案的优点在于,不用改业务系统的任何代码,对源库的影响也很小,CDC 模式只需要一个 binlog 读取权限就能完成。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
1.2 技术选型的考量
先说说为什么不用 DataX 或者 Kettle 这种离线同步工具。DataX 做全量迁移确实非常稳,但是增量同步需要配合调度系统定时跑批,做不到秒级延迟。Kettle 的 ETL 能力很强,可视化的方式拉数据很方便,但是对 binlog 这个级别的变更捕获支持有限。
Canal 是另一个常见选择,但是它主要把数据先推到消息队列或者直接消费,目标端往往需要自己写消费逻辑。Flink CDC + Flink JDBC 的做法是让 Flink 同时承担"捕获变更"和"写入目标库"两个职责,链路最短,维护成本相对也低。
达梦数据库的 JDBC 驱动基本兼容 MySQL 的 JDBC 用法,但是细节上有不少坑,比如批量提交方式、事务隔离级别、特殊关键字等。这个项目把踩坑过程都记录下来,后面会详细展开。
2. 环境准备与基础配置
2.1 达梦数据库环境
达梦数据库的版本我当时用的是 DM8,建议也使用 DM8 及以上的版本。在正式开始之前需要确认以下几点:
- 达梦数据库已安装并初始化实例。
- 服务器上能用 disql 或达梦管理工具正常连接。
- 有足够权限创建用户、表,以及查看相关系统表。
安装达梦的过程这里不赘述,网上有很多教程,但有一点必须提醒:数据库字符集尽量和 MySQL 保持兼容。如果 MySQL 用的是 utf8mb4,达梦这边建议使用 UTF-8。否则中文或者表情符号写入后,可能在目标端直接变成乱码。
初始化实例完毕之后,建议顺手做一次是否启用归档检查。Flink CDC 虽然不要求目标库开归档,但如果后续要做故障恢复,达梦这边的备份策略还是要提前准备的。
2.2 MySQL 开启 binlog
Flink CDC 读取 MySQL 的数据变更必须依赖 binlog,所以要确保 MySQL 开启了 binlog 并且使用 row 格式。
ini复制[mysqld]
server-id=1
log-bin=mysql-bin
binlog_format=row
binlog_row_image=full
expire_logs_days=7
修改完配置文件之后重启 MySQL 服务,然后执行:
sql复制SHOW VARIABLES LIKE 'log_bin';
SHOW VARIABLES LIKE 'binlog_format';
如果 log_bin 是 ON,binlog_format 是 ROW,就说明配置正确。这里有一个非常容易被忽略的点:CDC 读取账号需要至少具备 SELECT, RELOAD, SHOW DATABASES, REPLICATION SLAVE, REPLICATION CLIENT 这 5 个权限。
sql复制CREATE USER 'flink_cdc'@'%' IDENTIFIED BY 'your_password';
GRANT SELECT, RELOAD, SHOW DATABASES, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'flink_cdc'@'%';
FLUSH PRIVILEGES;
2.3 达梦开启 CDC 的特殊处理
这个项目标题里专门提到了"达梦JDBC",但如果你是从 MySQL 同步到达梦,源端只需要 MySQL 的 CDC 能力,达梦那边作为目标库,理论上不需要开启 CDC。不过我在调研过程中发现很多人在搜索"达梦数据库如何开启 CDC",这里也顺便说一下。
达梦自身支持 CDC 功能,主要用于其他数据源同步到达梦的场景。如果你将来要做"到达梦的反向同步",可以在达梦上开启日志归档然后安装 CDC 组件。但本项目只涉及达梦作为目标端,所以只用 JDBC 连接写入即可,不需要额外开启达梦的 CDC。
3. Flink CDC 同步方案设计
3.1 整体链路架构
整个同步链路可以描述为:MySQL binlog → Flink CDC Connector → Flink 算子处理 → Flink JDBC Connector → 达梦数据库。
Flink CDC 的 Table API 在 2.x 版本之后整合到了 Flink 的官方连接器中,如果你用的是 Flink 1.17 或更高版本,建议直接引入 flink-connector-mysql-cdc 这个依赖。之前的版本需要单独引入 flink-cdc-connectors 相关依赖。
Flink 提供的 CDC 连接器可以无缝对接 Flink SQL,支持将 MySQL 表映射为 Flink 动态表,然后通过 JDBC 连接器写入达梦。核心思路如下:
sql复制CREATE TABLE mysql_source (
id INT,
name STRING,
age INT,
create_time TIMESTAMP(3),
PRIMARY KEY (id) NOT ENFORCED
) WITH (
'connector' = 'mysql-cdc',
'hostname' = '192.168.1.100',
'port' = '3306',
'username' = 'flink_cdc',
'password' = 'your_password',
'database-name' = 'source_db',
'table-name' = 'source_table',
'scan.startup.mode' = 'initial'
);
scan.startup.mode 这里我使用的是 initial,意思是全量加增量。它会先把当前表中已存在的历史数据全部读一遍,然后自动切换到 binlog 增量模式。如果只需要增量,可以使用 latest-offset。
3.2 JDBC Sink 的三种实现方式
Flink 写达梦有三种常见方式:
-
Flink JDBC SQL Connector:用官方提供的
jdbcconnector,写 SQL 的方式实现。 -
Flink JDBC DataStream API:通过
JdbcSink或自定义RichSinkFunction实现,灵活度更高。 -
自定义批量写达梦:在 Sink 函数内部维护一个批量缓存,攒够一定条数再统一通过达梦 JDBC 批量提交。
我最终采用了第一种方式,因为项目整体都是用 Flink SQL 做的,链路清晰且易维护。
Flink JDBC Sink 写法如下:
sql复制CREATE TABLE dm_sink (
id INT,
name STRING,
age INT,
create_time TIMESTAMP(3),
PRIMARY KEY (id) NOT ENFORCED
) WITH (
'connector' = 'jdbc',
'url' = 'jdbc:dm://192.168.1.101:5236',
'table-name' = 'SYNC_USER',
'username' = 'SYNC_USER',
'password' = 'your_password',
'sink.buffer-flush.max-rows' = '1000',
'sink.buffer-flush.interval' = '2s',
'sink.max-retries' = '3'
);
然后一条 INSERT 语句就可以把数据从 source 表接入 sink 表:
sql复制INSERT INTO dm_sink SELECT id, name, age, create_time FROM mysql_source;
3.3 为什么选择 Flink SQL 而不是 DataStream
之前做过 Canal + 自研写入的同步任务,一个表一个表地写代码,维护成本太高了。Flink SQL 方案只需要维护 DDL,业务表变更时改一下 DDL 就能继续跑。
另外 Flink SQL 天然具备分布式 checkpoint 能力,配合 exactly-once(确切一次)或者 at-least-once(至少一次)语义,可以控制同步的精度。对于大多数业务数据同步场景,at-least-once 加上目标表主键幂等,就能保证最终一致。
4. 核心配置与参数调优
4.1 Flink JDBC 连接器参数详解
Flink JDBC 连接器写入达梦时,有几个参数是决定性能的关键。直接说结论。
-
sink.buffer-flush.max-rows:攒多少条数据触发一次写入。默认是 100,我调到 1000,因为达梦 JDBC 批量提交时,一次提交 1000 条比一次提交 100 条的性能提升非常明显,但也不是越大越好,超过 5000 后,单批次执行时间变长,如果中途失败,回滚代价也变大。 -
sink.buffer-flush.interval:即使数据量没达到max-rows,超过这个时间也会强制刷出去。默认是 1s,我保持这个值。对于一些低频变更的表,这个参数能保证延迟不超过 1 秒。 -
sink.max-retries:写入失败后重试次数。如果达梦短暂不可用,3 次重试通常能扛住。但要注意,如果连接连续失败,Flink 任务会直接失败,这时候需要靠 checkpoint 恢复。 -
sink.parallelism:写入的并行度。这个取决于目标端处理能力和表数量,我通常设置为源端并行度的一半。
下面是几个容易踩坑的参数,逐个说明:
sink.buffer-flush.max-rows 千万不要为了极致性能调到 10000 以上。原因在于,如果这批数据里恰好有几条违反了主键约束,达梦会在整批提交时报错,Flink 重试时会把整批数据再执行一遍,这种放大效应在目标表有大量冲突时会非常致命。
jdbc.url 可以追加 rewriteBatchedStatements=true 吗?答案是不需要。MySQL 的 JDBC 驱动有这个参数,达梦的 JDBC 驱动是否支持取决于驱动版本。建议在达梦 JDBC URL 中追加 batch=true 或参考驱动文档,这能让驱动内部使用更高效的原生批量接口。
4.2 达梦 JDBC 驱动引入与连接参数
达梦 JDBC 驱动在安装目录下的 drivers/jdbc 目录中,常见的是 DmJdbcDriver18.jar。需要在 Maven 或直接放到 Flink lib 目录中。
code复制<dependency>
<groupId>com.dameng</groupId>
<artifactId>DmJdbcDriver18</artifactId>
<version>8.1.2.192</version>
<scope>system</scope>
<systemPath>${project.basedir}/lib/DmJdbcDriver18.jar</systemPath>
</dependency>
如果你用 Flink SQL 客户端,更简单的方式是直接把 DmJdbcDriver18.jar 放到 $FLINK_HOME/lib 目录下,然后重启 SQL 客户端。
连接 URL 需要注意:
code复制jdbc:dm://192.168.1.101:5236
达梦默认端口是 5236。驱动类名是 dm.jdbc.driver.DmDriver,和 MySQL 的 com.mysql.cj.jdbc.Driver 不同。
使用 DBeaver 连接达梦时,如果在"数据库驱动"设置里选择达梦自带的驱动,通常能自动识别。如果你想在 DBeaver 里先验证一下数据,这比在 Flink 里反复调试更方便。
4.3 幂等写入设计与主键策略
同步场景中最容易出现的问题就是重复数据。Flink CDC 在发生故障恢复时,可能会从 checkpoint 恢复并且重复读取一部分 binlog 日志,导致目标库出现重复数据。为了避免这个问题,目标表必须有主键或者唯一索引。
达梦表的主键和 MySQL 类似,直接用:
sql复制CREATE TABLE SYNC_USER (
ID INT PRIMARY KEY,
NAME VARCHAR(100),
AGE INT,
CREATE_TIME TIMESTAMP
);
然后在 Flink JDBC Sink 的 DDL 里声明:
sql复制PRIMARY KEY (id) NOT ENFORCED
只要写了主键,Flink JDBC Sink 会自动将 INSERT 语句转换为 UPSERT 语句。达梦支持 MERGE INTO,所以 Flink 驱动会尝试用主键来做更新。但是,如果表里主键冲突,Flink 的 JDBC Sink 究竟会更新还是报错?实测下来,达梦 JDBC 连接器在 upsert 的时候能正确实现"有则更新,无则插入",但这要求主键字段必须和源表主键一致。
如果源表和目标表的主键不一致,就会出现大问题。比如 MySQL 表的主键是 id,达梦表的主键也是 id 但业务唯一键是 user_code,那么同步过程中如果源表更新了 user_code 字段,Flink 还是按照 id 去匹配目标表,导致目标表出现两条 user_code 相同的记录。这个一定要在设计阶段确认好。
5. 实操过程与问题排查
5.1 完整启动流程
这里直接贴出我整个项目的启动步骤,照着做基本能跑通。
第一步:检查 MySQL binlog 状态
sql复制SHOW MASTER STATUS;
记录下当前的 binlog 文件和位置,如果 Flink 任务中途挂了,需要从 checkpoint 恢复时可以用到。
第二步:在达梦中创建目标库和表结构
sql复制CREATE USER SYNC_USER IDENTIFIED BY "your_password";
CREATE SCHEMA SYNC_SCHEMA AUTHORIZATION SYNC_USER;
然后创建表结构,字段类型尽量与 MySQL 保持一致。
第三步:编写 Flink SQL 任务
启动 sql-client.sh,依次执行 source、sink 和 insert 语句。
Flink SQL Client 默认没有开启 checkpoint,需要在启动前配置:
code复制execution.checkpointing.interval: 60s
execution.checkpointing.mode: EXACTLY_ONCE
state.backend: rocksdb
建议在生产环境一定开启 checkpoint,否则任务重启后没法续跑。
第四步:观察数据
可以用 DBeaver 连到达梦库,直接查看同步是否完成。
5.2 常见报错与解决方案
整理一下我实际踩过的坑。
报错一:-6602 达梦错误码
这个错误码非常经典,很多人都会遇到。它的含义是"数据溢出"或"非法的日期时间类型"。常见场景是:MySQL 的 timestamp 有默认值 CURRENT_TIMESTAMP,同步到达梦后,达梦对 TIMESTAMP 的处理没有那么宽松。
解决方法是,目标表中的时间字段不要直接使用 TIMESTAMP,可以改成 DATETIME 或 VARCHAR,或者在建表时明确指定时间精度。另外注意 MySQL 的 int + 5 这类隐式类型转换,如果在同步过程中出现了 -6602,优先检查类型映射。
报错二:JDBC connector 异常:Batch entry 0 INSERT ... was aborted
这是批量插入时某一条数据违反了约束。比如主键冲突、非空字段为空、字段长度超限。
遇到这种问题,最有效的排查方式是:先把 sink.buffer-flush.max-rows 调成 1,逐条插入,找到具体是哪一条出问题。找到后检查源数据字段值。
报错三:Caused by: java.sql.SQLException: 网络通信失败
可能是达梦连接数满或者 SYSSSO 用户连接异常。直接使用 DBeaver 或 disql 手动连接,如果也连接失败,说明是达梦实例的问题。如果只有 Flink 连不上,需要检查是否有连接数限制。
报错四:Table 'xxx' doesn't exist
达梦对 schema 的解析和 MySQL 不一样。JDBC URL 里如果没指定 schema,Flink 可能找不到表。在达梦中,默认 schema 和用户名一致,所以需要检查 Sink 表名是否带上了 schema 前缀:
sql复制'table-name' = 'SYNC_SCHEMA.SYNC_USER'
报错五:达梦中使用 bt 查看 core 文件定位 SQL
这个问题属于达梦运维诊断范畴。当达梦数据库崩溃或发生严重错误时,会在安装目录下生成 core 文件。用 bt 工具可以分析 core 文件中的堆栈信息,定位出是哪个 SQL 语句导致了宕机。实操时,通常是查看 $DM_HOME/bin 目录下的 core 文件,然后执行:
bash复制cd $DM_HOME/bin
./bt core.xxx
bt 工具会打印出当时的线程栈和 SQL 上下文。我们在 Flink 同步过程中,如果批量写入 SQL 特别复杂,且出现达梦连接进程崩溃的情况,这个命令能快速定位。
5.3 大事务与延迟问题
Flink CDC 是边读边写,但源库出现大事务时,binlog 里的记录可能非常多,Flink 的任务会出现背压,sink 到达梦的延迟变大。这个问题有过一次深刻教训。
当时有一张流水表,业务方做了每季度一次的归档操作,一次性 UPDATE 了上百万行记录。MySQL binlog 瞬间生成了几个 GB 的数据,Flink CDC 任务处理不过来,checkpoint 开始超时。Flink 为了安全不断重启,反而拖慢了消费进度。
处理方案是:
- 给 Flink TaskManager 分配更大的内存。
- 提高 sink 并行度。
- 将
sink.buffer-flush.max-rows从 1000 调到 2000。 - 最根本的方案是在源库侧控制大事务,尽量分批提交。
排查背压时可以直接看 Flink Web UI,如果某个 operator 的 backlog 持续增长,说明消费能力不足。可以在 SQL 中通过 /*+ OPTIONS('scan.incremental.snapshot.chunk.size'='8096') */ 调整 CDC 读取的 chunk 大小来优化。
6. 性能优化与批量写入实测
6.1 三种批量写入方式的性能对比
我先用 Flink JDBC Sink 默认模式测试,发现写入速度只有每秒 3000 条左右,这个速度对百万级全量迁移来说太慢。后来尝试了几种优化方式。
- 方式一:使用官方 JDBC Sink,
buffer-flush.max-rows调到 2000,关闭自动提交。 - 方式二:使用自定义 RichSinkFunction,内部用达梦 JDBC
addBatch+executeBatch,每批次 1000 条。 - 方式三:使用 Flink JDBC Sink 但换成达梦原生批量参数。
实测结果如下:
| 写入方式 | 批量大小 | 吞吐量(条/秒) | 稳定性 |
|---|---|---|---|
| 官方 JDBC Sink(默认) | 100 | 3000 | 稳定 |
| 官方 JDBC Sink(调优) | 2000 | 12000 | 稳定 |
| 自定义 batch 写入 | 1000 | 18000 | 较稳定 |
| 官方 JDBC Sink + 达梦 batch 参数 | 2000 | 15000 | 稳定 |
自定义 batch 写入性能最好,但从工程化角度,官方 JDBC Sink 已经能满足绝大多数场景。如果你的同步量非常大,建议使用自定义 batch 写入,同时把失败重试、回退逻辑做好。
6.2 自定义写入 Demo
给大家贴一段简化版的自定义 Sink,方便理解批量写入原理。注意实际使用时需要对异常处理、主键冲突、事务回滚做更多处理。
java复制public class DmBatchSink extends RichSinkFunction<RowData> {
private static final int BATCH_SIZE = 1000;
private Connection connection;
private PreparedStatement preparedStatement;
private List<RowData> buffer = new ArrayList<>();
@Override
public void open(Configuration parameters) throws Exception {
connection = DriverManager.getConnection("jdbc:dm://192.168.1.101:5236", "SYNC_USER", "your_password");
connection.setAutoCommit(false);
}
@Override
public void invoke(RowData value, Context context) throws Exception {
buffer.add(value);
if (buffer.size() >= BATCH_SIZE) {
flush();
}
}
private void flush() throws Exception {
for (RowData row : buffer) {
// 根据 RowData 拼装 preparedStatement 参数
preparedStatement.addBatch();
}
preparedStatement.executeBatch();
connection.commit();
buffer.clear();
}
@Override
public void close() throws Exception {
if (buffer.size() > 0) {
flush();
}
if (preparedStatement != null) {
preparedStatement.close();
}
if (connection != null) {
connection.close();
}
}
}
这套代码的要点是:每个批次必须在一个事务中提交,防止数据不一致。addBatch 和 executeBatch 是 JDBC 批量写入的基础,提前把预编译 SQL 准备好,性能比单条执行提升数倍。
6.3 JDBC URL 参数优化
达梦 JDBC 连接 URL 支持一些参数,能进一步优化写入:
code复制jdbc:dm://192.168.1.101:5236?batch=true&fetchSize=100&bufferRows=1000
batch=true 开启批量模式,bufferRows 设置驱动端缓冲行数。实际测试中,打开这些参数后性能提升约 20%-30%。具体参数名建议根据你的达梦版本查一下官方文档,不同版本略有差异。
7. 数据一致性保障
7.1 同步延迟监控
同步链路跑起来了之后,要时刻关注延迟情况。Flink Web UI 上可以看到 currentFetchEventTimeLag 和 currentEmitEventTimeLag 两个指标。
currentFetchEventTimeLag:从 MySQL 捕获事件到 Flink 收到事件的时间差。currentEmitEventTimeLag:从捕获事件到 Flink 向下游发送事件的时间差。
运维上建议配置告警,当 currentFetchEventTimeLag 超过 5 分钟时触发通知。因为延迟通常不是突发的,而是逐渐累积的,提前发现及时扩容并行度或优化 SQL,避免业务侧数据长时间不同步。
7.2 失败恢复的幂等保障
Flink 任务如果失败,重启之后默认从最近成功的 checkpoint 恢复。配合目标表主键的 upsert 语义,即使重复读取了部分 binlog,最终写入的结果也是正确的。
但有一种情况比较危险:如果目标表没有主键,Flink JDBC Sink 不会生成 upsert 语句,而是直接 INSERT。这样恢复之后会出现大量重复数据。解决方案是:目标表必须有主键或唯一约束,否则同步无法自愈。
7.3 全量增量切换时的数据一致性
scan.startup.mode=initial 模式下,Flink 会先做全量 snapshot,再切换到增量 binlog 读取。这个切换动作是自动完成的,底层会把 snapshot 期间产生的 binlog 记录缓存下来,等 snapshot 完成之后再统一处理。
实测下来这个机制很可靠,但在 snapshot 阶段对源库 MySQL 的压力不小。如果源表很大,建议在业务低峰期执行首次全量同步。同时 MySQL 的 binlog_row_image 必须设置为 full,否则 snapshot 期间的 binlog 记录可能包含不了完整的前后镜像,影响写入。
8. 扩展与后续优化方向
8.1 多表同步的处理
如果同步的表很多,不可能一张表一个 Flink 任务。可以把同库的多张表放到一个任务里,用 Flink SQL 的 pattern source_table 通配,或者用 DataStream 的 TableEnvironment 做多表注册。更简单的做法是使用 Flink CDC 的多表路由能力,在 source DDL 中不指定具体表,而是用正则。
sql复制CREATE TABLE mysql_source (
...
) WITH (
'connector' = 'mysql-cdc',
'hostname' = '192.168.1.100',
...
'table-name' = 'db_.*'
);
不过这种方式在目标端表名映射上会复杂一些,需要自己写路由逻辑。如果表数量不多,建议每个同步链路单独建任务,运维时能更清晰地区分问题。
8.2 异构表结构处理
源表和目标表字段不一样,是最常见的需求。比如 MySQL 表里有 create_time 是 datetime,目标表里要改成 varchar 存储。直接在 Flink SQL 的 SELECT 中进行转换:
sql复制INSERT INTO dm_sink
SELECT
id,
name,
DATE_FORMAT(create_time, '%Y-%m-%d %H:%i:%s') AS create_time_str
FROM mysql_source;
这个转换过程不会影响 CDC 的增量读取性能,因为转换发生在 Flink 算子内部,不涉及额外的数据查询。
8.3 遇到达梦连接工具的选择
在开发和排错阶段,工具选型也很关键。我试用过几种连接达梦的工具,实际体验参考如下:
| 工具 | 优点 | 缺点 |
|---|---|---|
| 达梦自带的 DM Manager | 功能最全,支持备份恢复、用户权限管理 | 界面老旧,偶尔卡顿 |
| DBeaver | 跨平台,支持多数据源统一管理 | 需要手动配置驱动,个别版本对达梦的字符集兼容一般 |
| Kettle | ETL 调试方便,可以图形化做数据转换 | 大数据量同步时性能一般 |
| disql 命令行 | 排查问题最快,可直接执行 SQL 和查看执行计划 | 没有图形界面,学习成本略高 |
如果在 Windows 上直连达梦,优先用 DBeaver,省心。如果在服务器上排查问题,用 disql 最直接。我在启动 Flink 任务之前通常会先用 DBeaver 连一下达梦,确认账号权限、表结构、字符集都没问题,再启动任务。
9. 项目经验总结
9.1 我自己踩过的几个坑
这个项目做了几周,最有价值的不是最终同步成功,而是中间踩过的那些坑。随便列几个:
-
达梦的 SQL 关键字非常敏感。
SYNC_USER这种表名看起来很普通,结果 USER 是达梦保留字,建表时 SQL 直接报错。解决办法是表名统一加前缀或者用双引号括起来。建议建表前把表名和字段名过一遍达梦的保留字列表。 -
MySQL 和达梦的日期类型默认值差异。 MySQL 里
DEFAULT CURRENT_TIMESTAMP非常普遍,达梦的TIMESTAMP默认值不支持函数,需要写成DEFAULT CURRENT_TIMESTAMP()或者通过触发器实现。我在同步建表时直接把这些默认值去掉了,由源数据带入。 -
达梦的 core 文件分析很重要。 曾经遇到一次达梦进程意外退出,当时急得团团转。后来通过
bt命令查看 core 文件,发现是批量插入时一条异常 SQL 触发了内部断言。从那以后,我在做大批量同步前一定会在测试环境跑一轮完整的批量写入压测,确认没有触发达梦内部 bug 的 SQL 模板。 -
批量插入的报错定位不要靠猜。 当
executeBatch报错时,JDBC 驱动通常会提示Batch entry X was aborted,但这个 X 是整批中的索引,不是业务主键。我习惯把batch调小一点,或者每次 flush 前打印首条和末条数据的关键字段,出错后能快速锁定问题数据范围。
9.2 同步任务运维建议
同步任务跑起来不是终点,运行期的稳定性才是关键。我建议做三件事:
第一,开启 Flink checkpoint 并且设置较短的超时时间。 Flink 默认 checkpoint 超时可能很长,一旦 checkpoint 卡住,整个任务会越来越慢。建议设置 execution.checkpointing.timeout: 10min,并配置失败后的自动重启策略:
code复制restart-strategy: fixed-delay
restart-strategy.fixed-delay.attempts: 5
restart-strategy.fixed-delay.delay: 10s
第二,给达梦连接池设置合理的超时和重连机制。 Flink 的 JDBC 连接如果长时间空闲,达梦服务端可能会断开连接。重新连接后第一笔写入会失败,如果重试次数不够,任务会挂掉。把 sink.max-retries 调到 5 或者更大,同时让 Flink 定期刷新连接。
第三,监控源库 MySQL 的磁盘空间和 binlog 保留时间。 如果磁盘满了,MySQL 会阻塞所有写入,业务先炸;然后 Flink CDC 读不到 binlog 也会积压。最好在监控系统里做好磁盘和 binlog 增长趋势的告警。
9.3 后续可以怎么扩展
这次做的是单表同步,如果后续要同步整个业务库,可以考虑引入 Schema Evolution 机制,在源表结构变更时自动更新目标表结构。目前 Flink CDC 社区对 schema 变更的支持还在不断完善中,临时方案是通过监听 binlog 的 DDL 事件,触发自己写的 DDL 同步逻辑。
另外,如果后续需要同步多份数据到多个目标端,比如既到达梦又到 PostgreSQL,可以把 Flink CDC 的数据先发到 Kafka,再由下游各取所需。这样源库 MySQL 只需要承受一份 binlog 读取压力,架构上也更清晰。但链路长了,运维复杂度也会上升。对于当前项目,MySQL → Flink → 达梦这个短链路是性价比最高的选择。
我在实际运行这个同步任务的过程中,最深的体会是:Flink CDC 本身非常成熟,难的不是把数据搬过去,而是理解源库和目标库在数据语义上的细微差异,并在设计阶段就把这些差异处理好。 同步链路跑通只是第一步,跑得稳、能自愈、出了问题能快速定位,才是真正的工程能力。希望这篇文章能帮你少踩一些坑。
