简介:面向需要实时数据同步的 Flink 开发者和数据工程师,一套基于日志解析的 FlinkCDC 达梦数据库同步方案,可解决传统轮询抽取延迟高、影响源库性能等痛点。核心包含 flink-connector-dm-cdc 连接器 jar 包及配套 DmJdbcDriver18 驱动,可直接接入 Flink 作业捕获达梦数据库的插入、更新与删除操作;另附 SQL 初始化脚本、参考程序压缩包与图文版用户手册,涵盖连接参数配置、JAVA 与 SQL 两种同步入口的落地用法。整套资源共 5 个文件,以 jar 程序包和 sql 脚本为主,辅以 docx 使用说明,压缩后大小约 35.48MB。当前已有 2083 人浏览学习,适合希望快速搭建达梦实时入仓、数据仓库同步或事件驱动类实时报表与监控告警场景的读者,尤其有助于降低驱动适配与作业联调的排障成本。
1. 达梦数据库实时同步:基于日志解析为什么比 JDBC 轮询更值得做
很多团队刚把 DM8 跑起来时,不管是在 Linux 上用安装脚本装完,还是 Windows 下拿着 x86_win_64.zip 解压部署,第一件事都是拿 Navicat 连上去开几张表,等业务一上量,立刻就要面对数据同步问题。这里说的同步不是一次性导数据,而是把源库的表变化持续推到下游,近实时生效。最常见的做法是定时任务按时间字段轮询增量,用一段时间就会撞上三件事:删除记录抓不到、大事务回滚没感知、手工改库相当于黑匣子。所以才有这篇要展开的 FlinkCDC + 达梦数据库的实时同步资源:走基于日志解析的路线,把 DM8 的 redo 与归档日志转成变更事件流,数据一致性和延迟都能兜住。适合已经部署 DM8、要往 MySQL、Kafka 或数仓同步数据的读者,也适合刚接触 CDC 想弄明白「日志从哪来、断点怎么记」的入门者。
2. 达梦基于日志解析的前提:redo、归档日志和 LSN 先对齐
2.1 redo、归档日志与 LSN:日志挖掘在 DM8 里长什么样
达梦和 MySQL 最大的差别,是没有 binlog 那种即开即用的逻辑日志。DM8 里事务提交先写 redo log,redo 是物理变更记录,记录的是数据页层面的修改,直接解析 redo 能拿到行变更,但非常底层。真正给同步工具用的,是归档日志,也就是把 redo 按时间窗持续复制出来的历史日志文件。FlinkCDC 对达梦数据库的适配,本质上是把 Debezium 风格的日志挖掘能力接进来:快照阶段先做一致性全量读取,增量阶段从某个 LSN 继续往后挖。
这里会涉及 LSN(Log Sequence Number),它是达梦日志的单调递增序列号。同步任务要从哪个位置继续,不是记时间戳,而是记 LSN。我拆这个资源时最直观的感受是:一份 FLinkCDC 同步任务如果把 LSN 搞错了,快照做完后往往抓不到任何增量,因为增量起点可能比当前日志还靠后。下面一段先用 SQL 确认实例是否处于归档状态,这是所有操作的第一步。
-- 以 DBA 身份登录后,检查当前实例的归档模式 SELECT ARCH_MODE, STATUS FROM V$DATABASE;ARCH_MODE 为 1 代表归档模式已开启,0 则没有开。很多场景下你打开数据库安装完,默认是非归档,FlinkCDC 的增量通道会直接失败或者一直空转。遇到这类情况,先不要怀疑连接器代码,第一步永远是确认达梦实例本身的日志状态。
如果没开归档,需要改 dm.ini 里的参数并重启实例,再添加归档目录:
-- 1. 在 dm.ini 中确认 ARCH_INI = 1,然后重启 DM 服务 -- 2. 重启后以 DBA 身份执行以下语句,添加归档配置 ALTER DATABASE ADD ARCHIVELOG 'DEST=/dm8/arch, TYPE=local, FILE_SIZE=1024, SPACE_LIMIT=10240'; ALTER DATABASE ARCHIVELOG;说明一下这条命令的含义:DEST 是归档日志落盘路径,需要提前建好并有写权限;FILE_SIZE 是单个归档文件大小上限,单位 MB;SPACE_LIMIT 是归档目录总容量上限,超过后可能有覆盖或阻塞风险,建议按业务增量大小留足余量。TYPE=local 表示本地归档,异构同步场景下一般够用。
达梦和 MySQL 在日志记录方式上还有一层差异:默认情况下,UPDATE 只记录被修改的列,DELETE 只记录主键,这样日志挖掘拿不到完整的一行前后镜像。为此需要在表级开启补充日志,这一步很多人会漏。检查不到前镜像时,Flink 端常常表现为 UPDATE 事件里只有一部分字段,或者即使事件拿到了,目标表的列也是空的。
-- 对需要同步的表开启补充日志,记录所有列的前后镜像 ALTER TABLE SCOTT.T_ORDER ADD SUPPLEMENTAL LOG DATA (ALL) COLUMNS; -- 查询是否已有补充日志组 SELECT * FROM DBA_LOG_GROUPS WHERE OWNER = 'SCOTT';据我实际拆连接器源码和踩过 DM8 环境的经验,上述两条语句几乎决定同步任务能不能在增量阶段活下来。开启补充日志后,日志文件会明显变大,这是预期内代价,不要因此关掉它,否则数据一致性没法保证。
2.2 准备动作清单:归档、补充日志、主键一个都别省
我在多个环境里部署时,习惯先把下面这张检查清单跑一遍,再开 Flink 作业。按表格逐项确认,能省下后面一大半排查时间。
| 检查项 | 具体方法 | 不满足的后果 |
|---|---|---|
| 归档模式 | SELECT ARCH_MODE FROM V$DATABASE | 增量阶段抓不到事件,任务空转 |
| 归档空间 | 检查 DEST 目录磁盘余量 | 日志写满后同步中断 |
| 补充日志 | DBA_LOG_GROUPS 视图查询 | UPDATE 前镜像不完整 |
| 主键或唯一索引 | DBA_INDEXES 查询目标表 | 快照分片失败、事件路由乱 |
| 驱动版本 | 检查 DmJdbcDriver18 与实例版本匹配 | 任务起不来或连接被拒 |
这张清单里,主键这一点容易被忽略。FlinkCDC 的快照阶段通常依赖主键或唯一索引对表做分片读取,没有主键的表要么走全表扫描单线程,要么直接不支持增量。达梦环境里,很多业务表确实没建主键,我的处理方式是在同步前和 DBA 确认能否加唯一索引,不能加就把这个表单独拿出来,用另一套 JDBC 轮询方案补偿。
完成以上检查后,再进入 Flink 工程搭建,否则后面所有参数调优都会建立在不稳定的沙地上。
3. 搭建同步工程:依赖引入、连接器配置与首个 Flink SQL 作业
3.1 依赖准备:把达梦驱动装进本地 Maven 仓库
第一步先解决驱动问题。达梦官方驱动 DmJdbcDriver18 不发布到公共 Maven 中央仓库,所以需要在工程里手动 install-file。我这里以 JDK 1.8 对应的驱动为例,如果你的服务端是 DM8 及以上,驱动 jar 一般就在数据库安装目录的 drivers/jdbc 下,Windows 安装包解压后同样能找到。
# 把达梦 JDBC 驱动装进本地 Maven 仓库 mvn install:install-file \ -Dfile=DmJdbcDriver18.jar \ -DgroupId=com.dameng \ -DartifactId=DmJdbcDriver18 \ -Dversion=8.1.2.140 \ -Dpackaging=jar执行成功后,在 pom.xml 里引入依赖。注意 groupId、artifactId 要和 install-file 时保持一致,版本号按你手头驱动实际版本替换,我这里 8.1.2.140 只是给一个结构示意,不建议无脑照抄。
<dependency> <groupId>com.dameng</groupId> <artifactId>DmJdbcDriver18</artifactId> <version>8.1.2.140</version> </dependency> <dependency> <groupId>org.apache.flink</groupId> <artifactId>flink-connector-dameng</artifactId> <version>替换为你下载的连接器版本</version> </dependency>Flink 算子任务运行时,驱动还会从 Flink 的 lib 目录加载。所以除了写进 pom,还要把 DmJdbcDriver18.jar 复制到 $FLINK_HOME/lib 下面,并在 YARN 或 Standalone 模式下重启 Flink 集群会话,否则提交作业时会报 ClassNotFound。这一步是最容易在本地编译通过、上集群就翻车的点。
3.2 Flink SQL 建源表和目标表:参数对照着看
我推荐用 Flink SQL 客户端做首次验证,比写 DataStream API 快得多。以下是一份最小可跑的作业配置,源表是达梦 SCOTT.T_ORDER,目标表是 MySQL 的 replica.t_order。
CREATE TABLE source_dm ( order_id BIGINT, user_id BIGINT, amount DECIMAL(10, 2), create_time TIMESTAMP(3), PRIMARY KEY (order_id) NOT ENFORCED ) WITH ( 'connector' = 'dm-cdc', 'host' = '192.168.1.20', 'port' = '5236', 'username' = 'SYSDBA', 'password' = '******', 'database-name' = 'DMSERVER', 'schema-name' = 'SCOTT', 'table-name' = 'T_ORDER', 'scan.incremental.snapshot.enabled' = 'true', 'scan.incremental.snapshot.chunk.size' = '8096' );这段 SQL 里几个关键字段说明:host 和 port 填达梦实例的监听地址,默认端口 5236;database-name 在达梦里对应创建实例时指定的数据库名,不是操作系统名;schema-name 对应达梦模式,达梦里模式通常就是用户,这里 SCOTT 是典型示例。主键声明即使源表没有也要给一个逻辑主键,否则全量快照阶段无法做分片。
目标表按 MySQL 侧建:
CREATE TABLE sink_mysql ( order_id BIGINT, user_id BIGINT, amount DECIMAL(10, 2), create_time TIMESTAMP(3), PRIMARY KEY (order_id) NOT ENFORCED ) WITH ( 'connector' = 'jdbc', 'url' = 'jdbc:mysql://192.168.1.30:3306/replica', 'username' = 'sync_user', 'password' = '******', 'table-name' = 't_order' ); INSERT INTO sink_mysql SELECT * FROM source_dm;作业提交后,Flink 会先进入快照阶段,也就是全表扫一遍源表写入目标端,扫完后自动转增量。第一次跑不要期望秒级看到增量,快照阶段根据表的数据量可能需要几分钟到几十分钟,这时候观察日志里是否出现 chunk 扫描记录,比盯下游数据更靠谱。
3.3 从快照到增量的衔接机制
我见过不少人第一次跑完,看到目标表里数据齐了就直接放行,结果第二天发现增量一直没有过来。核心原因是对「增量从哪个 LSN 开始」缺一个验证动作。连接器在快照阶段结束时,会记录当前源库的最新 LSN,然后从这个位置起消费归档日志。验证方式很简单,快照完成后手动往源表更新一行,看目标表是否在数秒内出现变化。
提示:如果等了几分钟都没变化,先查源库归档日志目录是否在持续产生新文件,再用第 2 章的补充日志开关排查前后镜像是否完整。
还有一种情况是快照阶段运行了很久才转增量,原因通常是表数据量大且 chunk.size 设置过大。这个参数我们放到下一章专门说。
4. FlinkCDC 参数调优:并行度、chunk 大小与 Checkpoint 这样设
4.1 影响吞吐的五个关键参数
达梦走日志挖掘,链路比 MySQL CDC 更吃配置。我把拆项目中实际调过的参数整理成一张速查表,其中有些参数名是连接器暴露透传给 Debezium logminer 的,不支持的版本会自动忽略,不影响作业启动。
| 参数 | 默认值 | 作用 | 调优建议 |
|---|---|---|---|
| scan.incremental.snapshot.chunk.size | 8096 | 快照阶段单次读取行数 | 大表单次 2000-4000 更稳 |
| scan.incremental.snapshot.enabled | true | 是否启用增量快照 | 保持 true,不要关 |
| parallelism | 1 | 源表并行读取并行度 | 按槽位和数据量调 2-4 |
| debezium.log.mining.batch.size.default | 512 | 每次日志挖掘的日志块数量 | 大事务多时调大到 1024 |
| debezium.log.mining.stride | 2048 | 日志挖掘扫描窗口步长 | 增量量大时调小,延迟敏感时调大 |
这些参数里最重要的是 chunk.size。它决定快照阶段每个分片读多少行,过大时单次查询内存压力大,过小时分片数太多浪费连接。我的经验是:亿级以下的表用默认 8096 没毛病,上了亿级反而调小到 2000 更顺,因为大分片在慢查询环境下会拖慢整个快照进度。
4.2 并行度提升的收益边界
很多人以为把并行度调高,日志挖掘速度就线性增长。实际情况是:达梦日志挖掘的并发能力受制于数据库自身的日志读取会话,连接器在单表场景下,并行度提升到 4 以后收益会明显衰减。你的资源里有多个同步表时,优先给不同表分配独立作业或独立 source,而不是无脑加大单表并行度。
-- 在 Flink SQL 中为 source 设置并行度的写法 SET 'parallelism.default' = '4';如果走 DataStream API,也可以在 env.fromSource 之后显式设置算子并行度。调完并行度,重点观察两个指标:一是 source 算子的 busy 时间占比,二是 Kafka sink 的发送吞吐。busy 时间接近 100% 而吞吐不涨,说明瓶颈不在并行度,而在日志挖掘或下游写入。
4.3 Checkpoint 参数与断点续传
实时同步最怕的就是作业重启后丢数或重复消费。Flink 的 EXACTLY_ONCE 依赖 Checkpoint,增量阶段的 LSN 就保存在状态里。以 Flink 1.15 以后的状态后端配置为例:
execution.checkpointing.interval: 10s execution.checkpointing.mode: EXACTLY_ONCE execution.checkpointing.min-pause: 5s state.backend.type: rocksdb这里的关键是 interval 不能太大。我之前见过默认 60 秒的配置,重启作业后会重新读取最近一分钟的日志,下游如果没做幂等,会出现大量重复数据。10 到 30 秒是增量同步的常规区间,低于 5 秒会导致频繁持久化状态,反而降低吞吐。RocksDB 状态后端在日志挖掘场景几乎必选,因为状态里缓存了表结构和分片进度,用内存后端容易在大状态恢复时触发 OOM。
5. 避坑:五个高频故障的记录与处理
5.1 作业启动报错:找不到达梦驱动类
现象:Flink 作业提交后秒级失败,日志里出现 ClassNotFoundException: dm.jdbc.driver.DmDriver,或者 JDBC connect 阶段直接报 connection refused,但用 Navicat 连接源库一切正常。
原因:第一种是驱动 jar 只写进了工程依赖,没有放进 Flink 运行时的 lib 目录;第二种是驱动版本和达梦服务端不一致,新版本 DM8 对旧驱动连接做了限制。
解决:把 DmJdbcDriver18.jar 复制到 $FLINK_HOME/lib 下并重启 Flink 集群;同时确认驱动 jar 的版本来自达梦安装包内自带的 drivers/jdbc,而不是网上下载一个来路不明的同名 jar。
5.2 快照阶段一直卡住,迟迟不进入增量
现象:日志显示一直在扫描 chunk,但目标表数据量不见涨,或者涨得极慢,几十分钟没有完成。
原因:多数是目标表没有主键或唯一索引,连接器退化为全表单线程扫描;另一个原因是 chunk.size 相对于表数据量设置过大,单次查询超时。
解决:先检查源表索引结构,能加主键就加;不能加就把 source 定义里的主键声明保留,并把 chunk.size 调小,比如从 8096 调到 2048。调整后重启作业,快照速度通常会有明显改善。
5.3 时间字段整体偏差 8 小时
现象:同步到下游后,所有 TIMESTAMP 字段比源库早 8 小时,看起来像时区错位。源库插入 12:00,目标表却是 04:00。
原因:达梦的 TIMESTAMP 不带时区,连接器默认按 UTC 解释并写入下游,而业务期望的是北京时间。这是时区解释问题,不是数据写错。
解决:在 source 表 DDL 的 WITH 参数里增加时区设置:
'server-time-zone' = 'Asia/Shanghai'设置完删除源表并重建,重新提交作业。注意这个参数对快照阶段已写出的数据不会做二次修正,需要全量重跑一遍。
5.4 执行 DDL 后作业直接失败
现象:业务侧执行了一条 ALTER TABLE ADD COLUMN,几分钟后 Flink 作业状态变为 FAILED,或者持续重启无法恢复。
原因:达梦的 DDL 事件解析在连接器里支持有限,加列、删列这类结构变更转换成 SchemaChangeEvent 时如果没有对应处理逻辑,作业就会抛出异常。
解决:短期做法是同步期间禁止对源表执行 DDL;中期做法是升级 flink-connector-dameng 到更新版本,并确认是否支持 DDL 透传。如果业务无法避免 DDL,我在实践中会拆成两步:先停作业,再执行 DDL,最后以新表结构重建 source 和 sink 并重启。
5.5 大事务提交后延迟飙升
现象:业务一次批量 UPDATE 几十万行,提交后下游延迟从几秒涨到十几分钟,之后慢慢恢复。
原因:日志挖掘对大事务的处理是把整个事务的变更全部重放,单事务事件量巨大时,挖掘和下游写入都成为瓶颈。
解决:调大日志挖掘批大小,适当提高 source 并行度。如果业务允许,建议把大事务拆成小批次提交。这里要说明,调大 batch.size 会增加单次内存占用,需要配合 Flink 作业的 TaskManager 内存上限调整,否则可能先触发内存溢出。
6. 验证同步正确性:两库对比与延迟观测
6.1 写一个最小校验脚本
同步任务上线后,我不建议只看任务状态为 RUNNING 就认定成功。最朴素也最有效的验证,是分别统计源库和目标库的行数,再做一次抽样校验。下面这段脚本用 disql 和 mysql 客户端分别取 count,实际使用时把连接信息换掉:
DM_COUNT=$(disql -s "SYSDBA/******@192.168.1.20:5236" -e \ "SELECT COUNT(*) FROM SCOTT.T_ORDER;" 2>/dev/null | grep -Eo '[0-9]+') MYSQL_COUNT=$(mysql -h192.168.1.30 -usync_user -p****** replica \ -N -e "SELECT COUNT(*) FROM t_order;") if [ "$DM_COUNT" = "$MYSQL_COUNT" ]; then echo "count ok: $DM_COUNT" else echo "mismatch: dm=$DM_COUNT mysql=$MYSQL_COUNT" fi这个脚本只验证总量,行级一致性用 count 是不够的,但对多数同步链路来说,count 对不上一定有问题,count 能对上再谈抽样比。要严格校验,可以取主键集合做差集,或者对关键字段做 md5 汇总后对比,这一步我通常在数据量较小的核心表上执行。
6.2 延迟观测的落地方法
延迟的直观判断,是往源表写一条测试行并观测落库时间。更客观一点的做法,是查询达梦当前的最新 LSN,再对比作业状态中记录的消费位点。达梦侧用 V$RLOG 视图:
SELECT CURR_LSN FROM V$RLOG;Flink 侧如果用了 Checkpoint,可以在状态后端里看到最近一次快照的 LSN,两者差值折算成时间,就是大致延迟。实操中我不太建议每次都去翻状态,更常用的是往源表插入带标识的测试数据,然后用下游查询对比时间差,整个过程十秒内完成,直观且不需要额外工具。
做完这些校验,我会把脚本固定到部署文档里。在那之前遇到过一张大表 count 一直相等、但明细对不上的惨案,后来排查是 DELETE 事件没有正常捕获,目标端多了一堆孤儿数据。从那以后我每次同步任务上线,都强制先跑一遍 count 校验,再插入一条测试行确认延迟,核完明细抽查才对下游放量。这套动作虽然琐碎,但能挡住大多数黑匣子一样的同步事故,希望帮到正在搭链路的人。
本文还有配套的精品资源,点击获取