数据集成产品的技术底座:从日志解析原理到 KFS 同步链路搭建(含建表与增删改查完整代码)

聊数据集成产品之前,先想一个更原始的问题:两个数据库,一个 Oracle 一个别的什么,业务还在不停往里写,你怎么让对端跟上?

全量好办,导就是了。麻烦的是增量------源库每秒都在变,你得在对端复现出一模一样的状态,还得证明它真的一模一样。这三件事(捕获、翻译、校验)是分开的,很多工具只解决其中一个半,剩下的靠你脑补。

下面按这三件事往下写,中间会落到具体产品上------电科金仓异构数据同步软件 Kingbase FlySync,简称 KFS。代码都是能直接跑的,我会说明哪段是干嘛的。

一、捕获:四种办法,只有一个能全占

要在源库感知到 DML,工程上就那几招。

时间戳轮询。 表上加个 update_time,定时按时间捞。最省事,也最不靠谱。行被物理 DELETE 之后你就再也查不到它了------这是结构性的缺陷,不是调参数能补的。同一毫秒里改了两次也可能漏。业务代码要是忘了更新这个字段,那这条变更对你来说压根不存在。报表侧准实时可以这么干,涉及钱的场景不行。

触发器。 建 INSERT/UPDATE/DELETE 触发器,把变更写进中间表。能抓到全部 DML,代价是每条 DML 都在同一个事务里多写一次,锁持有时间变长。并发一上来 TPS 肉眼可见地掉,而且它改了源库的对象------核心系统基本不会让你这么干。

应用双写。 写源库的时候顺手写一份目标库。技术上完全可行,但等于把分布式事务的复杂度塞进业务代码。两阶段提交、幂等、乱序,哪一条都够喝一壶。而且存量数据你还是得单独搬。

解析增量日志。 Oracle 读 redo,MySQL 读 binlog,SQL Server 走 CDC。日志本来就是数据库写给自己做崩溃恢复用的,事务边界、提交顺序、前后镜像这些元信息天然齐全。你只是旁路读一份,不进事务、不持锁、不动业务代码。

第四条是唯一一个能同时占到"非侵入 + DML 全覆盖 + 亚秒延迟 + 保序"的。KFS 就是走这条,源端跑个进程读日志,源库里不会多出任何一张中间表、任何一个触发器。

这里有个衔接点容易被忽略。全量搬迁不是孤立的第一步------KFS 启动全量时会记下当时的日志位点(Oracle 记 SCN,MySQL 记 binlog 位置),全量搬完从这个位点往后追增量,中间那段"边搬边改"的数据靠日志重放补上。所以搬迁全程业务不用停写。

反过来讲,这也意味着源端日志保留多久,直接决定你这个方案有多大的容错空间。归档被清了,位点就断了,只能推倒重来。这个数得在设计阶段算,不是等运维打电话给你的时候才算。

二、翻译:中间那层格式到底解决什么

Oracle 的 redo、MySQL 的 binlog、SQL Server 的 CDC,三种东西长得完全不一样。要是每一对源-目标组合都做一次直连转换,做 N 种源 M 种目标就是 N×M 的工作量,加一种源就得改 M 处。

KFS 在中间插了一层,叫 KUFL。源端解析器把各家日志翻成统一的 KUFL 事件写盘,每个事务一个 seqno,严格递增;目标端只认 KUFL,按 seqno 顺序重放。N×M 变成 N+M,新接一种源只要写一个解析器。

写盘这个动作看起来不起眼,其实顺手解决了两件事。

头一件是断点续传。网断了、目标库重启了、同步进程崩了,都不用从头来,从 last applied seqno 接着走就行。我遇到过一次目标端存储打满,清完空间起来接着跑,中间那几小时的变更一条没丢------这在纯内存队列的方案里是想都别想的。

另一件是能看。变更以结构化事件的形式明明白白躺在磁盘上,你可以把某个 seqno 的事务直接打印出来,看它到底解析成了什么。这一条在排查的时候真救命,后面实操会用到。

顺便说下应用端的并发。完全串行重放,顺序是保证了,吞吐上不去;无脑并行,顺序乱了数据就错了。KFS 的做法是按冲突关系分通道------改同一行的保持顺序,不冲突的可以并行。所以配置里那个通道数不是越大越好,得看你业务的写入热点长什么样。改订单表的热点集中在几个大客户上,你开 16 个通道也快不了多少。

三、校验:别拿"能查到"当结论

第三个命题最容易被跳过。目标端 SELECT 出来有数据,只能说明链路通了,说明不了数据对。

先说怎么比。两边各扫一遍全表逐行比对,是最笨的办法,大表跑一次几小时,IO 顺带打满,跑的时候业务还得跟着抖。KFS 是按主键分段做摘要(MD5)比对,先比段,段对不上再下钻到行,计算量一下就压下来了。它也支持抽样,只抽一部分段,日常巡检够用;正式割接前我建议还是跑全量,别省这点时间。

再说比出来不一样怎么办。得有修复通道,能生成补偿 SQL 把目标端补齐。另外比对最好能在同步跑着的时候做------割接前夜做最终核验,业务还在写,总不能让人家停下来等你比完。

四、源端前置:这几项不开,后面白干

Oracle 的 redo 默认只记 rowid,不记被改行的主键值。rowid 传到对端没有任何意义。所以必须开补充日志,让 redo 带上主键列和唯一索引列的值。不开的话,同步出去的 UPDATE 在目标端根本定位不到改哪一行。

sql 复制代码
-- 库级最小补充日志
ALTER DATABASE ADD SUPPLEMENTAL LOG DATA;
-- 主键和唯一索引列补日志,强烈建议开
ALTER DATABASE ADD SUPPLEMENTAL LOG (PRIMARY KEY, UNIQUE INDEX) COLUMNS;

SELECT SUPPLEMENTAL_LOG_DATA_MIN, SUPPLEMENTAL_LOG_DATA_PK FROM V$DATABASE;

-- 同步专用账号,权限按最小集给
CREATE USER FLYSYNC IDENTIFIED BY "Sync#2026";
GRANT CONNECT, RESOURCE, SELECT ANY TABLE TO FLYSYNC;
GRANT SELECT ON V_$DATABASE TO FLYSYNC;
GRANT SELECT ON V_$ARCHIVED_LOG TO FLYSYNC;
GRANT EXECUTE ON DBMS_LOGMNR TO FLYSYNC;

归档模式必须开,保留时间我一般按 3 倍窗口留。同步中断里最常见的一类就是归档被清,SCN 接不上,只能重新初始化。

MySQL 做源端时查这三项,道理一样------ROW 格式的 binlog 才有完整的行镜像:

sql 复制代码
SHOW VARIABLES LIKE 'log_bin';
SHOW VARIABLES LIKE 'binlog_format';        -- 必须是 ROW
SHOW VARIABLES LIKE 'binlog_row_image';     -- 必须是 FULL

CREATE USER 'fs_user'@'%' IDENTIFIED BY 'Sync#2026';
GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'fs_user'@'%';
GRANT SELECT ON *.* TO 'fs_user'@'%';
SHOW GRANTS FOR 'fs_user'@'%';

binlog_row_image 不是 FULL 的话,UPDATE 拿不到完整前镜像,目标端应用的时候会出问题。

系统层面三件事,都是老生常谈但真有人栽:别用 root 跑同步程序;文件句柄调到 65535,不然启动阶段报资源不足,报得还特别隐晦;所有节点开时钟同步,跨机时间偏差会让校验结果出现你解释不通的差异。

bash 复制代码
useradd flysync
echo 'flysync' | passwd --stdin flysync
su - flysync

ulimit -n 65535
systemctl status chronyd
# 端口规划:rmi 11000,源端 Kufl 3112,目标端 Kufl 3115
# 管控台 Console 默认 HTTP 8089 / HTTPS 8088,Manager 8090

五、建表:类型映射和两个必须提前定的事

拿订单表举例。源端 Oracle:

sql 复制代码
CREATE TABLE APP_ORDER.ORDER_INFO (
  ORDER_ID    NUMBER(18)     PRIMARY KEY,
  CUST_ID     NUMBER(18)     NOT NULL,
  AMOUNT      NUMBER(18,2)   DEFAULT 0 NOT NULL,
  STATUS      VARCHAR2(16)   DEFAULT 'INIT' NOT NULL,
  REMARK      VARCHAR2(512),
  CREATED_AT  DATE           DEFAULT SYSDATE NOT NULL,
  UPDATED_AT  TIMESTAMP(6)   DEFAULT SYSTIMESTAMP
);

CREATE SEQUENCE APP_ORDER.SEQ_ORDER_ID
  START WITH 1 INCREMENT BY 1 NOCACHE NOCYCLE;

目标端 KingbaseES(后面简称 KES)。我习惯显式建序列而不是用自增类型------序列起始值能手动对齐源端,割接之后应用直接写目标库不会撞号:

sql 复制代码
CREATE USER flysync WITH PASSWORD 'Sync#2026';
CREATE SCHEMA app_order AUTHORIZATION flysync;

CREATE TABLE app_order.order_info (
  order_id   BIGINT        PRIMARY KEY,
  cust_id    BIGINT        NOT NULL,
  amount     NUMERIC(18,2) DEFAULT 0 NOT NULL,
  status     VARCHAR(16)   DEFAULT 'INIT' NOT NULL,
  remark     VARCHAR(512),
  created_at TIMESTAMP     DEFAULT CURRENT_TIMESTAMP NOT NULL,
  updated_at TIMESTAMP     DEFAULT CURRENT_TIMESTAMP
);

CREATE SEQUENCE app_order.seq_order_id
  START WITH 1 INCREMENT BY 1 NOCACHE NOCYCLE;

类型映射不是照着表格抄那么简单,说几个我实际踩过的。

NUMBER(p,s) 一定要落到 NUMERIC(p,s)。金额字段用浮点的话,月底汇总对账会出现 19.9999998 这种东西,财务能追着你问一整天。

Oracle 的 DATE 是带时分秒的,落 KES 得是 TIMESTAMP 而不是 DATE。这个错特别隐蔽------不报错,同步也正常,就是时间看着差一天,等你发现的时候已经跑了一周了。

VARCHAR2(n) 对 VARCHAR(n),KES 里 n 是字符数不是字节数,存中文很舒服,不用再算 UTF-8 三个字节那一套。CLOB 对 TEXT,大字段得单独拎出来评估,它拖同步吞吐拖得挺厉害。

还有两件事得在建表阶段就定下来。

序列起始值对齐。 割接后应用会直接往目标库写,序列还从 1 开始的话,第二天订单号就撞了:

sql 复制代码
-- 源端取当前最大值
SELECT MAX(ORDER_ID) FROM APP_ORDER.ORDER_INFO;
-- 假设返回 892300,目标端序列留余量:
-- CREATE SEQUENCE app_order.seq_order_id START WITH 893000 INCREMENT BY 1 NOCACHE NOCYCLE;

无主键表。 前面说过,变更定位靠主键或唯一索引。没主键的表,UPDATE 和 DELETE 只能退化成全列匹配,慢不说,碰到重复行直接报错。这类表要么补个代理主键,要么干脆排除出同步范围,别留在那当定时炸弹。

大小写也算是半个坑。Oracle 表名默认大写,KES 那边建小写的话要靠过滤器转换(参数名以你手上的官方文档为准,别照抄我的):

ini 复制代码
svc-remote-filters=casetransform
property=replicator.filter.casetransform.to_upper_case=false

我的建议是建表时就统一口径,比事后挂过滤器省心得多。

六、flysync.ini:核心就这一个文件

放在运行用户家目录下,源端目标端各一份,靠 role 区分。源端(master):

ini 复制代码
[defaults]
install-directory=/home/flysync/kfs
profile-script=~/.bash_profile
rmi-port=11000

[orcl2kes]
role=master
master=10.10.5.49
members=10.10.5.49
kufl-port=3112
replication-host=10.10.5.49
replication-port=1521
replication-user=FLYSYNC
replication-password=Sync#2026
datasource-type=oracle
datasource-oracle-service=orcl
oracle-extractor-method=redo
svc-extractor-filters=replicate
property=replicator.filter.replicate.do=APP_ORDER.*

目标端(slave):

ini 复制代码
[defaults]
install-directory=/home/flysync/kfs
profile-script=~/.bash_profile
rmi-port=11000

[orcl2kes]
role=slave
master=10.10.5.49
master-kufl-port=3112
members=10.10.3.200
kufl-port=3115
replication-host=10.10.3.200
replication-port=54321
replication-user=flysync
replication-password=Sync#2026
datasource-type=kingbase
kingbase-dbname=testdb
svc-remote-filters=casetransform
property=replicator.filter.casetransform.to_upper_case=false

说三个参数。kufl-port 是本机 KUFL 监听端口,目标端的 master-kufl-port 必须指向源端的 kufl-port------这是拉数据的地址,对不上就永远拉不到,我第一次配就栽在这,查了半天才发现是两个端口写反了。replicate.do 支持正则,做表级过滤,不想同步的表在这里拦掉,比灌进去再删强。oracle-extractor-method 设成 redo 就是走日志解析。

装:

bash 复制代码
cd KingbaseFlySync-replicator/tools
./fspm validate        # 先校验,能提前揪出大部分低级错误
./fspm install
source ~/.bash_profile
# license 文件放到 $HOME/kfs/ 下
replicator start
fsrepctl services

存量数据走离线初始化,追平了再切在线:

bash 复制代码
replicator start offline
fsrepctl -service orcl2kes status      # 盯 appliedLastSeqno 和 appliedLatency
fsrepctl -service orcl2kes online      # 切增量实时

改配置有固定流程,别改完直接重启:

bash 复制代码
cd ~/kfs/flysync-replicator/bin
./replicator stop
cp ~/flysync.ini ~/flysync.ini.bak     # 先备份
vi ~/flysync.ini
./fspm update                          # 让配置生效
./replicator start

同步跑着的时候覆盖配置文件,是典型的"服务起不来但日志里啥也看不出来"的诱因。另外 replicator stop 作用于这台机器上所有同步服务,敲之前先想想这台机器还跑了哪些链路。

七、增删改查:四个动作验一遍

状态 ONLINE 之后别光看控制台那个绿灯,真刀真枪写几条。

增:

sql 复制代码
INSERT INTO APP_ORDER.ORDER_INFO
  (ORDER_ID, CUST_ID, AMOUNT, STATUS, REMARK)
VALUES
  (APP_ORDER.SEQ_ORDER_ID.NEXTVAL, 10001, 1999.00, 'INIT', 'sync test 01');
COMMIT;

查,目标端立刻看:

sql 复制代码
SELECT order_id, cust_id, amount, status, remark
  FROM app_order.order_info
 WHERE cust_id = 10001;

改和删:

sql 复制代码
UPDATE APP_ORDER.ORDER_INFO
   SET STATUS = 'PAID',
       AMOUNT = 1899.00,
       UPDATED_AT = SYSTIMESTAMP
 WHERE ORDER_ID = 1;
COMMIT;

DELETE FROM APP_ORDER.ORDER_INFO WHERE ORDER_ID = 1;
COMMIT;

单条通了只说明链路是活的,想知道吞吐还得造量。插 5 万行看看多久追平:

sql 复制代码
INSERT INTO APP_ORDER.ORDER_INFO (ORDER_ID, CUST_ID, AMOUNT, STATUS)
SELECT APP_ORDER.SEQ_ORDER_ID.NEXTVAL,
       10000 + MOD(ROWNUM, 500),
       ROUND(DBMS_RANDOM.VALUE(10, 5000), 2),
       'INIT'
  FROM DUAL CONNECT BY ROWNUM <= 50000;
COMMIT;

两端各跑一次汇总,快速对个账:

sql 复制代码
SELECT COUNT(*) AS cnt, SUM(amount) AS total FROM APP_ORDER.ORDER_INFO;   -- 源端
SELECT COUNT(*) AS cnt, SUM(amount) AS total FROM app_order.order_info;   -- 目标端

DDL 同不同步由过滤器决定,可以试一条看配置对不对:

sql 复制代码
ALTER TABLE APP_ORDER.ORDER_INFO ADD (CHANNEL VARCHAR2(32) DEFAULT 'APP');

提一句:目标端表结构如果是你手工建好的,这条 ALTER 传过去会重复执行然后报错。生产上我一般把 DDL 同步关掉,表结构变更走评审和人工执行,别让源端一个随手 ALTER 把链路搞挂。

数据没到目标端的时候,正确姿势是先确认源端解析出来没有,而不是先翻配置:

bash 复制代码
kufl -service orcl2kes list -low 0 -high 5

输出长这样(节选):

复制代码
SEQ# = 2 / FRAG# = 0 (last frag)
- EVENTID = ora:86281679:86281703
- TYPE = com.kingbase.flysync.replicator.event.ReplDBMSEvent
  - ACTION = INSERT
  - SCHEMA = APP_ORDER
  - TABLE = ORDER_INFO
  - ROW# = 0
    - COL(1: ORDER_ID) = 1
    - COL(3: AMOUNT) = 1999.00
    - KEY(1: ORDER_ID) = 1

这就是前面说的可观测性。每个事务一个 seqno,ACTION 区分 INSERT/UPDATE/DELETE,列值和主键都摊开给你看。判断逻辑也就清楚了:KUFL 里有、目标库没有,那问题在应用端,去看目标端日志和 discard 文件;KUFL 里压根没有,问题在采集端,回头查补充日志和过滤规则。

再看状态:

bash 复制代码
fsrepctl -service orcl2kes status

我一般盯三个值:appliedLastSeqno(已应用的最大事务号)、appliedLatency(延迟)、pendingError(有没有卡住的错误)。判断追平要隔 30 到 60 秒执行两次看 seqno 有没有推进,只盯一次延迟数字容易被骗。

八、校验、监控和几个坑

跑稳之后做一次正式校验:

bash 复制代码
./fsrepctl checksum --source-db=src_conn --target-db=tgt_conn --table=app_order.order_info

差异数为 0 才算过。割接前夜跑全量并留档报告,日常巡检抽样就行。

运行期盯两类指标,吞吐和水位:

bash 复制代码
./fsrepctl metrics                                  # 实时吞吐量
fsrepctl -service orcl2kes status -name stores      # KUFL 水位、seqno 区间
kufl -service orcl2kes list -sum                    # 链路同步统计

stores 里 maximumStoredSeqNo 和 minimumStoredSeqNo 的差值要是持续变大,说明目标端跟不上了,要么入库慢,要么已经卡在某个错误上,得马上看。

几个坑,按我遇到过的频率排:

归档保留不足,SCN 接不上,只能重新初始化------这类最多。无主键表定位失败,建表阶段就该处理掉。大事务把 KUFL 撑爆,单事务几百万行那种,开事务分片,同时从应用层把事务粒度拆小,两头一起治。多对一汇聚的时候不同源库的自增主键天然会撞车,这是设计问题,配置救不了。跳过事务那类命令直接造成数据缺口,走审批、留痕、双人复核,别图快。

写在最后

回到开头那三件事。日志解析管捕获,非侵入还保序;KUFL 管翻译,顺带把断点续传和可观测性也一并给了;摘要比对加自动修复管校验,让结果可验证而不是靠信心。

挑这类数据集成产品的时候,我一般就问三个问题:捕获是不是非侵入的、中间那层能不能看和回溯、有没有自带的校验和修复闭环。这三条决定了它是让你安心睡觉的工具,还是又一个需要你时刻盯着的风险源。

相关推荐
程序员塔塔喵1 小时前
【MySQL篇|第七篇】MySQL事务
数据库·mysql·mysql事务
独隅2 小时前
Claude API 实战教程:从入门到完整项目
android·java·数据库·claude·claude code
_孤傲_2 小时前
快速掌握 MySQL 语法
数据库·后端·mysql
数字新视界4 小时前
一体化机柜在数据中心运维中的创新应用与优势分析
大数据·服务器·数据库·动环监控系统·动力与环境监控系统
SelectDB4 小时前
从 StarRocks 走向 Apache Doris:如何实现引擎平滑替换与架构升级?
大数据·数据库·数据分析
为思念酝酿的痛4 小时前
MySQL的操作
数据库·mysql
猫咪很可爱5 小时前
Oracle ORA-4031 错误分析与处理:SGA 内存不足导致 update/exp/expdp 失败
数据库·oracle
小豆庄5 小时前
视频素材处理时的隐私盲区:云处理与本地处理的边界在哪
数据库·图像处理·视频·敏捷流程
Umi·6 小时前
MySQL库表操作
数据库·mysql