从Oracle到KingbaseES,一条KFS链路把我从熬夜对数里捞了出来
去年接了一个保险行业的迁移项目源端是一套跑了快十年的Oracle存储过程三百多个表八千多张核心业务一天24小时不能停。甲方最开始给的方案是用传统的导出导入工具做T1批量同步我做了两天的数据一致性验证光是核对主键错位的问题就核到夜里两点。后来我换了思路上了电科金仓的异构数据同步软件KFS才算把这个项目从泥潭里拖出来。这篇文章不是官方通稿是我作为一个一线实施人员把KFS当成一个工具而非产品用了一段时间之后的完整复盘。我会贴出建表语句、增删改查的验证脚本、链路配置的实际参数还有踩过的坑。一、先说清楚KFS是个什么东西很多人第一次接触KFS会把它跟ETL工具混在一起这是不对的。ETL的核心是抽取-转换-加载本质上是定时跑批而KFS是基于日志解析的CDC变更数据捕获工具它做的事情用一句话概括实时地把源数据库的增删改操作原样搬运到目标数据库两边保持秒级甚至亚秒级的一致。KFS的定位是对标Oracle GoldenGate的这点在电科金仓官方的技术白皮书里写得很明确。两者的能力模型确实接近都是解析源端日志、事务级重放到目标端。但KFS有几个更适合国内落地场景的特性我在后面会逐一展开。1.1 支持的数据源KFS支持30余种数据源作为同步的源端或目标端我实际用过的包括Oracle源端解析Redo Log支持RAC集群MySQL源端解析BinlogSQL Server源端解析事务日志KingbaseES金仓数据库既可以做源也可以做目标MongoDB源端解析Oplog支持文档模型到关系模型的转换注意KFS是电科金仓的商业软件不是开源产品没有社区版可以白嫖。这一点在项目预算评估的时候要说清楚别拿它跟开源方案比价格它的价值点在商业支持和企业级特性上。1.2 核心能力清单用一张表总结KFS的主要功能特性能力维度具体支持情况同步模式全量搬迁 增量实时同步支持先全量后增量的无缝衔接拓扑结构一对一、一对多、多对一、级联、双向数据类型CHAR/VARCHAR/NUMBER/DATE/INT/FLOAT 等基础类型BLOB/CLOB/TEXT/XML大对象GIS几何类型SEQUENCE数据库对象表、索引、视图、同义词、存储过程、函数迁移场景下可同步对象定义DDL同步源端ALTER TABLE等结构变更自动同步到目标端一致性校验四种校验模式精简、详细、增量、快照存量校验支持MD5摘要比对断点续传支持基于位点记录中断后可从上次位置继续目标端优化批量提交、小事务合并、Statement缓存、多通道并行写入运维管理Manager管理节点 Console图形化控制台最后一张表里的几个目标端优化技术是我在这个项目里感触最深的后面会展开讲。1.3 和同类工具的对比很多人问KFS跟OGG、跟开源方案比怎么样。我的实际感受是对比OGGKFS在Oracle到KingbaseES这条链路上异构转换是开箱即用的不需要像OGG那样配置复杂的映射文件。OGG做异构同步字段类型映射、字符集转换这些都要手动调KFS内置了这些转换规则。另外KFS的图形化Console对运维人员友好很多OGG的命令行ggserr.log排查问题新手要学一阵子。对比开源方案开源的CDC工具不是不能用但一旦遇到大对象、GIS类型、或者DDL同步这些复杂场景基本上都要自己写代码补。KFS把这些问题在产品层面解决了运维成本是降下来的。二、KFS的架构拆解2.1 设计逻辑KFS的架构设计有一个很清晰的哲学同步通道是单向的数据流向是确定的。这样做的好处是避免了双写导致的数据冲突风险。架构上分为三个层次┌─────────────────────────────────────────────────────┐ │ 源端接入层 │ │ 解析数据库日志Redo Log / Binlog / WAL / Oplog │ │ 非SQL查询抽取对源库性能影响极小 │ └──────────────────┬──────────────────────────────────┘ ▼ ┌─────────────────────────────────────────────────────┐ │ 同步引擎层核心 │ │ 数据格式转换 / 字段映射 / 主键冲突处理 / 事务排序 │ └──────────────────┬──────────────────────────────────┘ ▼ ┌─────────────────────────────────────────────────────┐ │ 目标端写入层 │ │ 批量写入 / 事务提交 / 多通道并行 │ └─────────────────────────────────────────────────────┘关键的一点KFS通过解析数据库日志捕获变更而不是通过SQL查询去轮询抽取。这个差异很重要。轮询抽取会给源库增加查询压力而且有时间窗口数据不是实时的日志解析是事件驱动的源库一有变更立刻就能拿到对源库的性能损耗几乎可以忽略。2.2 核心组件KFS的部署由三类组件构成Manager管理节点负责全局配置、任务调度、监控告警。默认HTTP端口8090。Console图形化控制台提供Web界面做链路配置和运维管理。默认HTTP端口8089HTTPS为8088。同步实例实际干活的进程每个同步链路对应一个或多个同步实例。支持多实例热备主实例挂了备实例自动接管。2.3 性能指标官方口径的性能数据增量同步在1C2G最小环境下可达50GB/天保持秒级延迟全量搬迁对外口径200GB/小时弱网环境支持断点续传和数据压缩带宽占用可降低60%这里有一个很多人容易犯的错误不要把增量同步的50GB/天线性外推到全量场景。全量搬迁受磁盘IO和网络带宽的限制跟增量完全是两个模型。我在项目里实际测全量搬迁千兆内网环境下跑到130GB/小时左右离200GB有差距但已经够用了。这个数字放在POC阶段实测最靠谱不要直接拿官方口径去跟甲方承诺。三、实操从建表到增删改查的完整链路下面进入实操环节。场景设定源端是MySQL 8.0目标端是KingbaseES V9用KFS做实时同步。为了演示得清楚我用一个简化版的订单系统做例子。3.1 源端MySQL建表-- 源端 MySQL 8.0 CREATE DATABASE IF NOT EXISTS trade_db DEFAULT CHARACTER SET utf8mb4; USE trade_db; CREATE TABLE orders ( order_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 订单号, user_id BIGINT UNSIGNED NOT NULL COMMENT 用户ID, product_name VARCHAR(200) NOT NULL COMMENT 商品名称, amount DECIMAL(12, 2) NOT NULL COMMENT 订单金额, status TINYINT NOT NULL DEFAULT 0 COMMENT 0-待支付 1-已支付 2-已发货 3-已完成, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, remark TEXT COMMENT 备注, PRIMARY KEY (order_id), KEY idx_user_id (user_id), KEY idx_create_time (create_time) ) ENGINE InnoDB DEFAULT CHARSET utf8mb4 COMMENT 订单主表; CREATE TABLE order_items ( item_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 明细ID, order_id BIGINT UNSIGNED NOT NULL COMMENT 订单号, sku_code VARCHAR(64) NOT NULL COMMENT SKU编码, quantity INT NOT NULL COMMENT 数量, unit_price DECIMAL(10, 2) NOT NULL COMMENT 单价, PRIMARY KEY (item_id), KEY idx_order_id (order_id) ) ENGINE InnoDB DEFAULT CHARSET utf8mb4 COMMENT 订单明细表;注意MySQL侧开启了Binlog这是KFS做增量同步的前提-- 确认binlog已开启KFS需要row格式 SHOW VARIABLES LIKE log_bin; -- 应为 ON SHOW VARIABLES LIKE binlog_format; -- 应为 ROW SHOW VARIABLES LIKE binlog_row_image; -- 建议 FULLBinlog的保留策略要跟KFS的抽取位点匹配。我在项目里踩过一个坑源库的binlog保留时间设的是3天而KFS因为网络问题断了5天恢复的时候位点对应的binlog已经被清理了只能重新做全量。这个教训写在这里后来者注意。3.2 目标端KingbaseES建表-- 目标端 KingbaseES V9 CREATE SCHEMA IF NOT EXISTS trade_db; SET search_path TO trade_db; CREATE TABLE orders ( order_id BIGINT NOT NULL, user_id BIGINT NOT NULL, product_name VARCHAR(200) NOT NULL, amount NUMERIC(12, 2) NOT NULL, status SMALLINT NOT NULL DEFAULT 0, create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, remark TEXT, PRIMARY KEY (order_id) ); CREATE TABLE order_items ( item_id BIGINT NOT NULL, order_id BIGINT NOT NULL, sku_code VARCHAR(64) NOT NULL, quantity INTEGER NOT NULL, unit_price NUMERIC(10, 2) NOT NULL, PRIMARY KEY (item_id) ); CREATE INDEX idx_orders_user_id ON orders(user_id); CREATE INDEX idx_orders_create_time ON orders(create_time); CREATE INDEX idx_order_items_order_id ON order_items(order_id);类型映射上MySQL的BIGINT UNSIGNED到KingbaseES对应BIGINTTINYINT对应SMALLINTDECIMAL对应NUMERIC。KFS内置了异构类型转换规则这些映射在做链路配置的时候会自动匹配不需要手动逐个字段指定。3.3 KFS链路配置在Console上配置同步链路的步骤我用文字描述关键节点添加数据源分别在Console上注册MySQL源端和KingbaseES目标端填写连接串、账号密码。KFS要求账号有读取日志的权限——MySQL侧需要REPLICATION SLAVE和REPLICATION CLIENT权限KingbaseES侧需要目标schema的读写权限。创建同步任务选择源端数据库、目标数据库、要同步的表支持按schema批量选择。这里可以配置全量增量的模式先执行全量搬迁完成后自动切换到增量同步位点无缝衔接。配置目标端写入策略KFS提供了几个关键的性能调优参数我在项目里是这样配的# KFS目标端写入关键配置同步实例的profile配置 # 批量提交大小每多少条记录提交一次太大内存吃紧太小性能差 apply.batch.size 500 # 小事务合并把短时间内的小事务合并成大批次写入 apply.merge.small.transaction true apply.merge.interval.ms 200 # 多通道并行写入按主键hash路由到不同通道并行写入目标库 apply.parallel.channel.count 8 # Statement缓存预编译SQL复用减少目标库解析开销 apply.statement.cache true # 大事务拆批防止单个大事务撑爆目标端事务日志 apply.big.transaction.split true apply.big.transaction.batch.size 10000这个配置在项目里跑下来高峰期促销时段源库TPS大概3000左右同步延迟稳定在200ms以内。3.4 增删改查验证链路配好之后先在源端造一批测试数据然后观察目标端-- 源端 MySQL USE trade_db; -- INSERT INSERT INTO orders (user_id, product_name, amount, status, remark) VALUES (1001, 无线降噪耳机, 1299.00, 1, 双十一活动订单), (1002, 机械键盘, 459.50, 0, NULL), (1003, 27寸4K显示器, 2199.00, 1, 加急); INSERT INTO order_items (order_id, sku_code, quantity, unit_price) VALUES (1, SKU-EAR-001, 1, 1299.00), (2, SKU-KB-087, 2, 229.75), (3, SKU-MON-4K27, 1, 2199.00); -- UPDATE UPDATE orders SET status 2, update_time NOW() WHERE order_id 1; UPDATE order_items SET quantity 3 WHERE item_id 2; -- DELETE DELETE FROM orders WHERE order_id 2; DELETE FROM order_items WHERE order_id 2; -- 再插入一条用于后续校验 INSERT INTO orders (user_id, product_name, amount, status) VALUES (1004, 人体工学椅, 1699.00, 1);-- 目标端 KingbaseES SET search_path TO trade_db; -- 验证INSERT同步结果 SELECT order_id, user_id, product_name, amount, status FROM orders ORDER BY order_id; -- 预期结果 -- order_id | user_id | product_name | amount | status -- -------------------------------------------------- -- 1 | 1001 | 无线降噪耳机 | 1299.00 | 2 -- 3 | 1003 | 27寸4K显示器 | 2199.00 | 1 -- 4 | 1004 | 人体工学椅 | 1699.00 | 1 -- 验证UPDATE同步结果order_id1的状态应该已变为2 SELECT order_id, status, update_time FROM orders WHERE order_id 1; -- 验证DELETE同步结果order_id2应该不存在 SELECT COUNT(*) FROM orders WHERE order_id 2; -- 预期为0 -- 验证关联表 SELECT oi.item_id, oi.order_id, oi.sku_code, oi.quantity FROM order_items oi WHERE oi.order_id 1;实际操作的时候我在源端执行完INSERT转到目标端查询数据已经在了延迟肉眼不可见。UPDATE和DELETE同理。这种操作即同步的体验跟以前用ETL工具定时跑批的感觉完全不同。3.5 大对象和特殊类型业务系统里免不了有大字段我专门测了一下-- 源端加一个大字段表 CREATE TABLE attachments ( attach_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, order_id BIGINT UNSIGNED NOT NULL, file_data LONGBLOB COMMENT 附件二进制内容, file_text LONGTEXT COMMENT 附件文本内容, PRIMARY KEY (attach_id) ) ENGINE InnoDB; -- 插入一条带大对象的数据 INSERT INTO attachments (order_id, file_data, file_text) VALUES (1, LOAD_FILE(/tmp/test_contract.pdf), REPEAT(这是一份很长的合同文本内容。, 1000)); -- 目标端查询验证 SELECT attach_id, order_id, LENGTH(file_data) AS data_len, LENGTH(file_text) AS text_len FROM attachments WHERE attach_id 1;BLOB和TEXT类型的同步是很多CDC工具的软肋KFS处理得没问题。我还测了一个更偏门的场景——GIS类型从Oracle同步几何字段到KingbaseES空间数据无损这个对水利、自然资源行业的项目很关键。3.6 DDL同步这是最让我省心的一个能力。在双轨并行期间业务系统还在迭代表结构随时在变。以前用别的工具源端改个字段两端要手动同步操作漏一次就出事。KFS的做法是-- 源端执行DDL ALTER TABLE orders ADD COLUMN pay_method VARCHAR(32) DEFAULT NULL COMMENT 支付方式; CREATE INDEX idx_orders_status ON orders(status); -- 目标端验证 SELECT column_name, data_type, is_nullable FROM information_schema.columns WHERE table_schema trade_db AND table_name orders AND column_name pay_method;DDL自动同步到目标端字段类型自动映射。这里有一个KFS内部的处理细节值得说一下DDL操作会被固定在通道0执行不会跟DML并发。这个设计的意图是避免DDL和DML同时执行导致目标端元数据冲突。我在项目里没有触发过这个边界条件但从设计逻辑上讲这个保护是必要的。四、数据一致性校验同步链路跑起来了接下来要回答甲方必问的问题怎么证明两边数据一致KFS内置了四种校验模式我在项目里四种都用了一遍校验模式比对内容适用场景代价精简校验只比两端表的行数快速巡检极快详细校验逐行逐列比对差异可标出割接前最终确认慢占数据库资源增量校验只比指定位点之后的增量数据日常监控快快照存量校验基于快照比对存量数据大表兜底中等实际的使用策略是日常跑增量校验每15分钟一次割接前跑一次全量的详细校验大表用快照存量校验做兜底。详细校验可以配抽样参数不用每次都全表扫# 详细校验配置示例 check.mode detailed check.sample.rows 10000 -- 抽样1万行 check.thread.count 4 -- 4线程并行比对 check.schedule 0 2 * * * -- 每天凌晨2点执行比对发现不一致的时候KFS支持自动修复根据比对结果生成差异清单然后把源端的正确数据同步到目标端把不一致的修掉。修复操作支持表级批量执行。我在割接前一晚跑详细校验8千多张表里查出37张有差异全部是目标端因为一次网络闪断丢了几个事务自动修复之后复测一致。如果没有这个能力这37张表就要人工导出导入工作量至少是半天。告警也提一句KFS的校验结果可以接邮件、短信、微信、钉钉不一致的时候会推消息给运维。五、高级特性与踩坑记录5.1 断点续传网络抖动在跨机房、跨省的场景下是常态。KFS的断点续传机制是这样的同步实例会持续记录当前的日志消费位点中断恢复之后从上次记录的位点继续解析不会从头再来也不会丢数据。在断点续传的基础上KFS还支持多实例热备主同步实例挂了备实例自动接管业务无感知。我在项目里没有触发过主备切换但做过演练模拟kill掉主实例进程备实例在30秒左右接管数据零丢失。5.2 大事务处理源库有一次批量更新涉及20万行对应的事务日志在短时间内集中产生。KFS的处理策略是拆批把这20万行的变更拆成若干小批次每个小批次独立提交。这样做的代价是总耗时变长了一点但目标库的事务日志不会暴涨主从复制延迟不会飙升。这个策略我觉得是对的。同步工具的首要目标是稳不是快。一个20万行的大事务如果原样透传到目标端目标库直接就被打趴下了。5.3 位点管理这是我在这个项目里学到的最值钱的一课。源库日志的保留策略必须大于同步链路的最大可容忍中断时间。用公式表达就是源库日志保留时间 KFS最长允许中断时间 安全边际我在项目里的配置是MySQL Binlog保留7天KFS告警阈值设为中断超过4小时就发短信。这样即使周末出问题也有足够的时间在日志被清理之前恢复链路。5.4 弱网环境跨地域同步的时候带宽是瓶颈。KFS支持传输压缩实测带宽占用可以降低60%左右。加上断点续传弱网环境下的同步稳定性是有保障的。官方口径说特定场景下同步延迟可以稳定在50ms以内我在跨城专线的环境下实测延迟在300ms到1s之间波动跟网络质量强相关。六、总结KFS用下来我的整体评价是它是一个成熟度比较高的企业级数据同步产品适合对数据一致性要求高、异构环境复杂、又不能停业务的场景。它的产品力体现在几个地方异构能力是开箱即用的30多种数据源、复杂类型、DDL同步不需要自己写转换代码运维门槛比OGG低图形化Console加上完善的监控告警新手也能快速上手一致性校验做得比较完整四种校验模式加自动修复割接前的对数工作量大幅降低容错能力扎实断点续传、大事务拆批、多实例热备生产环境该有的都有不足的地方也有价格不便宜小团队或者预算紧张的项目可能会犹豫另外Console的易用性虽然比OGG好但有些高级配置还是要改配置文件界面上做不了。如果你正在做一个数据库迁移或者实时数仓的项目源端和目标端是异构的业务又不能停KFS值得放进POC的候选名单里。先拿几个典型的业务表跑一遍全量加增量的完整流程再跑一次详细校验数据自己会说话。