PostgreSQL码农集散地

金仓 KFS 探秘 | 异构数据同步的核心技术

异构数据同步从「能同步」到「敢切换」之间,隔着四道工程难关。今天不谈功能列表,只谈金仓 KFS 在每个难关上做了什么技术选择,以及这些选择在真实场景里意味着什么。


凌晨三点,电话响了。核心库 12 TB,日增 300 G,月末跑批会在 40 分钟内灌进 100 G 以上的单次日志增量。迁移窗口给了 4 小时,业务方的原话是: 「可以慢一点,但不能丢一条,也不能回不来。」

这种场景,我见过太多次。方案讨论会上,大家争的往往不是「用哪个工具」,而是三个更朴素的问题: 变更怎么捕获?峰值怎么扛住?一致怎么证明? 如果再加上一句「万一不行怎么退回去」,就是四个。

今天我想拆解一下金仓的 Kingbase FlySync(简称 KFS) , 它为了回答上面四个问题采用了哪些核心技术:旁路日志解析、KUFL 中间文件、seqno 时间轴、事务级并行入库。

本文速览

  1. 捕获层选「旁路日志解析」 —— 因为只有物理日志里同时存在事务边界、提交顺序和行级前项/后项,这是触发器、时间戳、批量 ETL 都给不了的东西。
  2. 传输层选「KUFL 落盘中间文件」 —— 不是为了缓存,而是为了把「源库故障」「网络中断」「目标库故障」三类问题解耦,让断点续传成为默认能力。
  3. 应用层选「事务级 + 分区并行」 —— 并行的前提是保序,KFS 用 seqno 与分区器把「无序的并行」收敛回「有序的结果」。
  4. 治理层选「可证明一致」 —— 四级校验 + 不停机快照校验 + 自动修复,把「我觉得一致」变成「我能出具报告」。

01 · 先说真正的痛点:数据同步的五道坎

很多人以为数据同步的难点是「写得快不快」。不是。真正的难点是在不打扰业务的前提下,把一件有状态的事持续做对。这中间有五道坎,每一道都能让项目翻车。

坎一:怎么「看见」变更——四条路线,四种代价

捕获变更有四种思路。它们不是「好坏之分」,而是「代价放在哪里」的区别:

  • 触发器 / UDF 双写:在源表上挂触发器,把变更写进中间表。代价是改表、加锁、递归风险,以及每次 DML 都要多一次写入——源库性能直接掉一档,而且触发器一旦漏配,就是静默丢数据。
  • 时间戳 / 自增列轮询:按 update_time > ? 拉增量。便宜,但物理上看不见 DELETE,也看不见「同一秒内的多次修改」,时间戳精度和时钟漂移就是天花板。
  • 批量 ETL:定时全量或增量抽取。适合 T+1 的数仓,不适合「切换窗口只有 4 小时」的迁移——它的粒度天生就是批,不是事务。
  • 日志解析:直接读 redo / binlog / WAL / xlog。这是数据库自己为崩溃恢复写的流水账,天然带事务边界、提交顺序、行级前后镜像。

Image
图 1|四条捕获路线的代价矩阵

图 1|四条捕获路线的代价分布。注意最后一列的「全绿」不是营销话术,而是原理决定的:物理日志是数据库为自身崩溃恢复写下的账本,事务边界与提交顺序是它天生具备的字段。走日志,等于借用了数据库的原子性与持久性。

这张表最值得记住的是 「断点与重放」 。触发器方案里,你没有一个全局坐标能说「我已经同步到这里了」;而日志解析有——Oracle 是 SCN,MySQL 是 binlog 位点,KES 是 LSN,KFS 在它们之上统一抽象成 seqno。没有这个坐标,「断点续传」这个词就是空谈。

数据库的日志,是唯一一份既记录「发生了什么」、又记录「以什么顺序、在哪个事务里发生」的数据。所有不走日志的同步方案,本质上都在事后重新猜测事务语义。

——这就是为什么 KFS 把捕获建在日志层

坎二:怎么「不丢不重」——断点必须落在可重放的坐标上

同步软件最怕的不是慢,是不知道自己做到哪儿了。网络抖一下、目标库重启一次、源库归档被 DBA 清掉一轮,如果状态只存在内存里,你唯一的选择就是「重来」。而重来意味着:目标端要么重复应用(主键冲突),要么先清空(业务停摆)。

所以第二个技术选择几乎是被逼出来的: 变更必须先落成持久化的中间文件,再谈传输。这就是 KFS 的 KUFL。

坎三:怎么「扛住峰值」——大事务不是慢,是内存与时延的雪崩

日常 20 MB/s,跑批时 100 G 一次性涌入。传统的实现崩溃原因几乎一样:读到超大事务 → 整个事务在内存里攒着等提交 → 内存打满 → GC 抖动 → 解析线程卡死 → 时延从秒级变成小时级 → 用户以为「同步挂了」。

这不是调大堆内存能解决的。堆内存调大只是把雪崩推迟十分钟。 必须让一个事务可以被切开、被分段确认——这是后文 KFS「大事务分片」要展开的核心。

坎四:怎么「证明一致」——不停机,还要能定位到行

迁移项目里最难回答的一句话是甲方问的: 「你凭什么说两边一样?」

停机比对是最容易的答案,也是最无法被用户接受的答案——它要求业务停摆,而且要停两次(比对 + 修复)。不停机比对则要面对一个悖论:你一边比,数据一边在变。所以真正的难点不是「比不比得了」,而是在一个会变化的数据集上,如何给出一个可以被复现、被定位、被修复的结论。

坎五:怎么「活着回来」——回切不是再同步一次

国产化替代的现场,最怕的不是切不过去,是切过去之后发现不对,却回不来。回切的本质是同步方向反转 + 断点接续 + 过滤器对称,缺一不可。很多项目在这一步翻车,不是因为工具有问题,而是因为方案设计时没人把它当一等公民。

02 · 架构:一条被设计成「可观测管道」的流水线

KFS 的组件划分很有讲究,但每个组件的边界划得也很有意思:

组件
职责
工程上的意味
Replicator
常驻后台进程,负责增量数据的解析、转换、传输、入库;一个进程内可跑多个 service(不同角色)
真正的干活单元。多线程支撑「一进程多服务」,一台机器就能承担一对多分发
Console
图形化管控平台,拓扑编排、状态监控、迁移与比对入口、权限
通过 JMX 远程调用 Replicator,默认 5 秒一轮采集——它是「观测面」,不参与数据面
Compare
一致性校验与修复
独立进程。因为比对吃 CPU,和管控平台混部会把监控界面拖死
元信息库
存拓扑、任务、比对结果
支持 KingbaseES V8 与 MySQL 5.7,可与业务库分离

这张表里藏着两个架构判断,值得单独说:

第一,管理面与数据面分离。 Console 不碰数据流,只做 JMX 观测与指令下发。所以你在监控界面卡住的时候,同步并不会停——这对生产环境是保命的。

第二,比对服务被单独拆出来。 因为比对会消耗大量 CPU,和管控平台放一起会让监控变慢。KFS 把「一致性校验」当成一件重活来对待,而不是监控面板上的一个按钮。


Image
图 2|KFS 组件与数据流

图 2|组件与数据流。蓝色是数据面(真正搬数据的那条线),下方灰色是管理面。把这两层分开画,是因为它们的故障模式完全不同:管理面挂了只是「看不见」,数据面挂了才会「丢数据」。KFS 让管理面只做 JMX 观测,本质上是在限制故障的传播半径。

为什么需要一个「中间文件」

第一次看到 KUFL 的人常问:为什么不直接在内存里传?落盘不是更慢吗?

答案是: 慢一点,但活得久一点。 KUFL 的存在,把三类本来会纠缠在一起的故障彻底解耦了:

  • 源库出问题(重启、归档被清、日志切换):源端 KUFL 已经落盘,解析恢复后从 seqno 接着写,不需要重新问数据库要日志。
  • 网络出问题:目标端 KUFL 有断点,重连后从断点续传,而不是「整段重放」——这正是「网络抖动不丢数据」的实现基础。
  • 目标库出问题:目标端 KUFL 变成缓冲区,故障期间持续堆积,恢复后按 seqno 追平。这也解释了手册里那条容易被忽略的容量公式——KUFL 需要预留磁盘,而且至少要放得下 7 天的日增量。

KUFL 里到底装了什么?我从 KFS 手册与运维命令中似乎能反推(注意我不是用 AI 逆向出来的, 真的是纯从用户角度反推出来的)出它的最小结构: 事务坐标(seqno)+ 事务边界 + 行级变更(含前项 BIC、后项)+ 分片号(fragno)+ 校验和(checksum) 。这五个字段决定了后面所有能力:能断点、能跳过、能校验、能冲突裁决、能定位到「某事务的某条语句」(fsrepctl online -frag-sql 14_0 这种精度,就是靠 fragno 撑起来的)。


Image
图 3|seqno 时间轴与断点续投

图 3|seqno 是一条可以被追溯的时间轴。红色是断点表 trep_commit_seqno,黄色是心跳表 heartbeat——前者回答「做到哪儿了」,后者回答「链路还活着吗」。断点续投之所以可靠,是因为它不只是记住了一个数字,还记住了这个数字对应事务的完整内容。

这里有个容易被忽略的工程细节:KFS 在源端每解析出一个事务,除了写 KUFL,还要更新数据库里的断点表。手册在性能调优章节专门提到,如果源库访问特别慢,可以把断点信息改存到文件(replicator.store.kufl.dataSource=file_metadata),代价是失去手动调整断点的能力。这种「用一致性换性能」的开关存在,本身就说明设计者对代价是清楚的。

03 · 六个硬问题,六种工程解法

前面都是铺垫。这一节开始,每一个小节都对应一个真实项目里会卡住人的问题:症状是什么、原理是什么、KFS 用什么手段解决、实施者要注意什么。

3.1 大事务:把一个 100 G 的事务切成能消化的块

症状很好认。 源端业务压力很大,同步服务一直是 ONLINE,但你连着敲几次 kufl list -last,最后一行的 TIME 死活不往前走。基本上就能判定:有大事务一直解析不出来,处于阻塞状态。

为什么会这样? 因为事务是原子单位。一个跑批事务写了 4 亿行,它就是一个事务;在它提交之前,任何严谨的实现都不能把「半个事务」投给目标端——那会破坏原子性。于是这半个事务的内容只能待在内存或临时区里等着,越等越大。

KFS 的做法是把「原子」和「不可分割的传输单位」这两件事拆开: 原子性由提交标记保证,传输与解析的粒度可以更细。具体是三层手段叠加:

  • 大事务分片:按行数切片(replicator.extractor.dbms.minRowsPerBlock,例如 1000 行一片),每片带一个 fragno;一个事务从「一坨」变成「一串」。这直接压住了内存峰值。
  • 多线程并行解析:切片之后,解析天然可以并行,多核才真正被用上。
  • 智能过滤前置:这是金仓的一项专利思路——在日志解析动作启动之前就把不需要同步的表/列过滤掉,而不是「先把所有日志还原成行,再逐行判断要不要」。跑批场景里,往往只有少数几张表需要同步,这一步省下的是数量级的 CPU 与内存。

三层叠加后能达到什么效果: 单次数据库日志增量 100 G+ 的跑批场景,同步时延 < 30 分钟。这个数字的意义不在于「快」,而在于它把「跑批期间同步会不会雪崩」变成了一个可以写进方案里的确定值。


Image
图 4|切开、并行、再收敛

图 4|切开、并行、再收敛。这张图想说明一件事:从「一个大事务」到「四条通道」,中间必须有两次收敛——一次在 KUFL(按 seqno 有序化),一次在分区器(相关事务归并到同通道)。少了任何一次,并行就会变成数据错乱。另外注意 fragno 与 sqlno 的存在,它们让运维可以精确跳到「某事务的第 14 片、第 0 条语句」。

3.2 并行入库:并行是假命题,除非你解决了顺序

这是我认为 KFS 设计里最值得学的一段,因为它没有回避矛盾。

目标端入库往往是全链路瓶颈:源端解析再快,目标库一条一条 apply 也追不上。直觉解法是「多开几个线程并行写」,但立刻撞上一个问题——一旦并行,事务的先后顺序就没了。而事务顺序恰恰是业务正确性的一部分:先插主表再插子表,顺序反了就是外键错误;先扣款再入账,顺序反了就是对账不平。

KFS 的答案是「分而治之,但按相关性分」,落在三个配置项上:

机制
配置
适用与代价
无差别轮询
RoundRobinPartitioner
目标表没有外键约束时最省心,事务被均匀打散到各通道,吞吐最高。有外键就会踩坑
按规则分片
ShardListPartitioner
 + shard.list
有外键时用它:把「表」或「模式」映射到固定通道,让有依赖的事务天然串行。规则优先级:直接映射 > (*) 统一映射 > (hash-method)
shard 归属改写
shardbytable
 过滤器
默认 shard 值是 schema,粒度太粗;这个过滤器把它改成表名,分区才有意义

还有一个细节容易被忽略:每个通道前面有个 barrier(屏障) 。状态里能看到 store.queue.barrier.0: false 这样的字段,含义是「该队列用量曾经满过,且还没降到一半以下」。它的作用是防止抖动——队列在临界点反复启停,比偶尔慢一点更伤性能。

顺便提一个场景, 也可能是你会踩到的“坑”:如果一个事务里频繁切换表名,且每个事务的变更行数很少(事务 1 改表 A、事务 2 改表 B、事务 3 又改表 A),批量提交就失效了,入库性能下降是正常现象,不是 bug。这类「多表混合小事务」的场景,堆通道数也没用,因为瓶颈在批量粒度而不在并发度。

注意 · 实施顺序有讲究

一定要「先完成初始搬迁,再做通道拆分」。如果你先拆了多通道再去做存量迁移,需要针对每个目标端服务分别做迁移,工作量翻倍且极易漏表。

还有一处细节, 逆向不可行: 单通道可以 fspm update 升到多通道,但多通道不能直接退回单通道(可能丢数据)。真要退,只能保留多通道配置、把 channels=1。

3.3 冲突:before image 才是钥匙

双活、双轨、灰度切换——只要目标端同时还有别的写入,冲突就是必然的。这时候大多数方案的应对是「报错、停下、等人」,而 KFS 选择把冲突变成一种可声明、可裁决的事件。

关键在于它能在源端把 before image(前项) 一并带出来。所谓前项,就是 UPDATE/DELETE 时那些用来做 WHERE 条件的旧值。有了它,「这一行在我这边到底是什么状态」才可判断。开启方式是源端 getBeforeCols=true 加一份 beforeColsRule.json,逐表指定要取哪些列的前项。

KFS 把冲突归成五类,并为每一类提供独立策略:

冲突类型
含义
可选的裁决方式
INSERTROWEXISTS
插入时唯一约束冲突(对方已有这行)
DISCARD / OVERWRITE / IGNORE / USEMAX / USEMIN 系列
UPDATEROWMISSING
更新时目标行不存在
DISCARD / OVERWRITE / IGNORE / USEMAX / USEMIN 系列
UPDATEROWEXISTS
行在,但前项列与目标当前值不一致(被人改过)
同上
DELETEROWMISSING
删除时行已不存在
DISCARD / OVERWRITE / IGNORE
DELETEROWEXISTS
行在,但前项不匹配
同上

其中 USEMAX:COL / USEMAXEQ / USEMIN / USEMINEQ 这四个策略特别值得说:它们把裁决权交给某一列的大小关系——「如果中间文件里这行的版本号大于目标库,就覆盖;否则丢弃」。这就是最经典的「最后写入者胜」的手工实现,但做成了配置。要注意两个限制:参与比较的列只支持整数、浮点、时间类型;列名不能带 : 或 ,。

但作为老司机, 我必须强调一句经验: 冲突处理是最后一道闸,不是第一选择。 当你在方案里大量使用 USEMAX 时,说明业务侧的写入边界没划清。真正的做法是通过分片键、单向写、或者业务改造让冲突不发生;工具的能力是用来兜底的,不是用来当架构的。

3.4 错误处理:别让一条脏数据拖垮整条链路

错误处理和冲突处理经常被混为一谈,其实两者层次不同: 错误处理是技术性的(这一条 SQL 目标库执行不了,怎么办),冲突处理是业务性的(这条数据谁说了算)。

默认情况下,目标端遇到主键冲突、非空约束、表不存在,同步会进入 ERROR:OFFLINE 等着人来处理。KFS 允许你把它变成策略:STOP(抛出,交旧策略)、IGNORE_ROW(只丢出错语句)、IGNORE_TRANSACTION(丢整个事务)、DISCARD_ROW / DISCARD_TRANSACTION(丢弃并单独记日志)。

这套机制的工程感体现在两个地方:

  • 按错误码配置,而不是一刀切。failure_policy_code.json 里可以写 "*.*": {"errorCode": "DISCARD_ROW"},粒度细到「模式.表 + 错误号」。标准流程:先全量 DISCARD_ROW 跑一轮 → 从 discard.log 里把错误号抄出来 → 再改成只对特定错误号跳过。
  • 防抖。 如果冲突数据太多,服务会反复 offline/online,性能断崖式下降。所以有个 error_handle_threshold(1~1000)控制连续报错的容忍度——这是个典型的「踩过坑才会想到」的参数。

还有一个必须知道的边界: Oracle 一个事务里出现出错的 DDL,只能整事务忽略,无法只跳过那一条 DDL。 这种限制不是实现偷懒,而是 DDL 与事务语义纠缠的本质所致。知道边界,方案才不会写错。

3.5 一致性:把「我觉得一致」变成「我能证明」

这一节是我认为 KFS 相对同类产品最有分量的一块,因为它把「校验」从一个动作做成了一条流水线。

先看它提供的四种比对模式,成本与能力差异很大,选错了不是慢一点的问题:

模式
怎么比
能定位到行吗
什么时候用
精简比对
按表比行数 / 摘要
不能
第一道筛子,快速找出「明显不一致」的表
MD5 比对
对行内容做摘要比对,无主键表不支持
到表级
中等规模、要速度;先筛出问题表
详细比对
逐行逐列比,支持抽样、where 条件、SCN 闪回
能,精确到行列
要出报告、要修复,必须用它
增量比对
按「变更行数阈值」或「时间」触发
能
长期运行期间的低成本持续巡检

真正解决「一边比一边变」这个悖论的,是快照校验:对 KingbaseES V8 目标端,可以手动把目标端服务 offline,让数据静止在一个时间点上做校验;比对并修复完成后,再用 online -skip-event [断点] 从校验结果里给出的断点恢复。这样既不需要停业务(只停了同步投递),又能拿到一个确定的、可复现的结论。

再往上,是任务组合策略(命令行 -ploy 0~5):精简 + MD5 先筛,详细比对定位,最后自动修复。这就是把「发现—定位—修复」串成一条可调度的流水线,而不是三次人工操作。

注意 · 支持单表级别刷新重来

reload-table 可以在不做校验的情况下强制把某张表刷成一致,代价是:1:N 场景会把所有 N 端一起刷;级联场景会刷完整条链;N:1 如果汇集到同一张表,会冲掉目标表全部数据;而且它不能与校验、初始化搬迁并发,也没有高可用保护(刷新中断会导致不一致,只能再刷一次)。

最后是修复的闭环。 手动修复支持按表、按页、按行、按操作类型(INSERT/UPDATE/DELETE)分别执行;自动修复则绑定比对任务,配置「等待时间」与「差异条数阈值」,差异数达到阈值才触发,避免为几行差异启动一整套修复流程。这一层设计的意义是:一致性维护从「项目交付时的一次性动作」变成了「运行期的常态能力」。

3.6 网络与隔离:当 TCP 只剩一个方向

如果你善于用第一性原理思考问题, 那么你一定会问前面所有的能力, 到底基于什么假设?

OK , 前面所有能力,都建立在一个默认前提上: 两端能互相说话。而在电力调度、军工、政务这类场景里,这个前提经常不成立——中间横着一台单向隔离装置(光闸),物理上只允许数据从左往右流,连 TCP 的回包都没有。

这时候你会发现,很多「实时同步产品」直接失效了:握手失败、确认收不到、重传无从谈起。

KFS 在这些场景里的做法很朴素,也很正确: 既然没有会话,那就不要会话。它把数据落成 KUFL 文件,让文件穿过隔离设备,对端再按文件消费。文件天然带边界、带校验和、带序号,不需要「确认」也能保证不丢不重——这是一种把可靠性从「协议层」下移到「数据层」的思路。


Image
图 5|单向隔离环境下的文件级投递

图 5|没有会话的同步。单向隔离环境里没有 TCP 回包,也就没有握手、确认与重传——可靠性只能从「协议层」下移到「数据层」:文件即消息,序号即顺序,校验和即确认。代价是能力必须被裁剪:实时同步可用,管控平台只能看到 III 区的部分指标,比对服务不可用,数据迁移只能用命令行的特殊模式。这不是产品缺陷,而是物理约束的诚实映射。

官方给的网络侧指标也值得放在方案里: 广域网传输约 4 倍压缩比,2 M 带宽即可支撑实时容灾。这个数字对分支机构、边缘节点、跨省专线场景是决定性的——它意味着你不必为了同步扩一条专线。

04 · 架构师的取舍:没有最优,只有匹配

做到这一步,你大概能感觉到 KFS 的设计哲学: 它不追求「一种方式做所有事」,而是把选择权连同代价一起交给你来判断, 相当灵活, 按需搭配。 既然要你选, 那我就深入一点, 这一节把最容易选错的三个决策点摊开。

取舍一:源端怎么抽——六种方式,能力边界完全不同

源端
抽取方式
前提
能力边界
部署约束
Oracle
LogMiner
归档 + 补全日志(MIN + PK)
不支持 LOB / DDL;性能低于 Redo
仅主库;可分离部署
Oracle
Redo
归档
支持 LOB 与 DDL,解析最快
通常需与库同机;分离部署需 ASM 账号
MySQL
集成 Redo
binlog_format=ROW
完整
必须与库同机
MySQL
分离 Redo
binlog_format=ROW
完整
需把日志拉到本地,占磁盘
SQL Server
CDC
开启 CDC
不支持 DDL
可分离
KingbaseES
xlogical(V7)
逻辑日志
原生 DDL 支持
必须与库同机
KingbaseES
decoderbuf(V8)
逻辑解码
DDL 依赖触发器
可分离

这张表很重要。因为选了能力边界不匹配的方式 —— 比如用 LogMiner 去同步一张带 BLOB 的表,跑到 90% 才发现不支持,那已经不是调优问题,是返工问题。

另外,源端还包括 Oracle RAC、MariaDB、PostgreSQL、PolarDB、DB2、达梦、OceanBase,以及 RabbitMQ / RocketMQ 这类消息中间件;目标端另有 Kafka、KADB 与 SQL 文件落盘。这个「源端多、目标端也多」的组合能力,这些才是 KFS 支持「异构」两个字的实际含量。

取舍二:装在一起还是分开装

维度
合并部署(与库同机)
分离部署
资源
与数据库抢 CPU / IO / 内存
互不干扰,故障域隔离
适用
Oracle Redo、MySQL 集成 Redo、KES xlogical
LogMiner、SQL Server CDC、KES decoderbuf
建议
仅在抽取方式强制要求时采用
默认推荐

这里有个容易被忽视的连带影响: 抽取方式决定了部署位置,部署位置又决定了网络带宽预算。LogMiner 可以分离,但意味着全量日志要走网络;Redo 解析快但要同机,意味着你得接受「同步进程和生产库共享一台机器」的心理门槛。这两件事必须在方案阶段一起定,不能等到实施。

取舍三:存量数据怎么搬

场景
停机窗口足够
停机窗口不够
品牌与结构一致
数据库备份还原
备份还原 + KFS 增量追平
品牌一致、结构不一致
KFS 工具
KFS 迁移 + KFS 同步
品牌不一致、结构一致(目标为 KES)
数据库迁移工具
迁移 + KFS 增量追平
品牌不一致、结构一致(目标非 KES)
KFS 工具
KFS 迁移 + KFS 同步
品牌与结构都不一致
KFS 工具
KFS 迁移 + KFS 同步

两条经验值: 存量小于 200 GB 时,用 KFS 自带的表数据迁移工具是划算的;超过这个量级,专业迁移工具的并行度和容错通常更值得。而且要注意 KFS 自带的 ddlscan 只搬表结构,不包含 PL/SQL 对象与外键——所以复杂 schema 的场景,结构迁移要么用 KDMS,要么用备份还原,KFS 只负责「增量追平」这一段。

注意 · 迁移工具的容错差异要提前说清

KFS 自带的表数据迁移目前没有可靠的断点容错,一个任务出错就得整体重跑。所以手册建议「少量多次」,每批最多 50 张表——这样出错时只需重跑一小批。这不是缺陷,是使用方式的约束,写进实施计划就不会出事。

05 · 双轨与回切:给「切换」留一条退路

前面讲的都是「怎么把数据搬过去」。但国产化替代真正的分水岭,是敢不敢把业务切过去。切换的勇气不来自信心,来自退路。

KFS 在这件事上的设计,是把「单向同步」变成「双向双轨」,并且承认它有三个阶段:

  1. 旧库为主、新库为备:数据实时一致,新库可以先承接一部分只读流量,用来验证兼容性与性能。
  2. 新库为主、旧库为备:稳定运行一段时间后切换主备角色,写流量进入新库,同时反向同步回旧库。这一阶段是「双轨」的核心——两边数据都是活的。
  3. 旧库下线:新库能力被充分验证后,拆除反向同步,旧库退役。

而回切,就发生在第二阶段:新库暴露了稳定性或性能问题,需要紧急撤回。 回切的速度与数据完整性,取决于你前面有没有把它设计进去。


Image
图 6|双轨并行与回切时序

图 6|双轨并行的三个阶段与回切的最后两步。注意第 ⑤ 步:反向同步不是「重新配一条链路」,而是同一套服务把角色互换——所以两端都必须按 role=master 配置。这也是为什么过滤器必须对称:反向时 rename 的映射要对偶、casetransform 的方向要相反,否则数据会在两个方向上来回「变形」。

回切的五个动作,一个都不能少

  1. 停业务:先停止源端业务系统的写入,让数据静止。
  2. 反转方向:用 repswitch 完成同步方向切换,而不是手工改配置——手工改配置最容易漏掉断点。
  3. 验证一致:用预先准备好的比对方案验证两端数据。这一步不能省,因为「方向反了」不等于「数据齐了」。
  4. 启动备用系统:拉起旧库侧的业务系统。
  5. 确认反向同步:确认新方向的数据流正常,才算回切完成。

还有一个常被忽略的后续: 回切之后怎么再切回来。仅需做一次存量差异迁移、验证一致后再切。原因很简单:回切期间产生的增量,可能有一部分没被覆盖到;如果不重新对齐就切,错误会以「数据不一致」的形式在后续爆发。

一个成熟的迁移方案,不是证明「一定能成功」,而是证明「失败了也能在 30 分钟内回到原点」。

—— 回切能力,是国产化替代项目里最难, 但最容易被低估的一项需求

06 · 把经验变成公式:容量、判据与调参顺序

这一节是给要写实施方案的人准备的。手册里散落着一些公式和判据,我把它们集中起来,因为它们决定你的方案是「看起来合理」还是「算得出来」。

三个容量公式

资源
公式
为什么是这样
内存
每服务 ≥ 2 GB;有大事务时 = 最大事务数据量 × 3(最低)/ × 5(推荐)
事务在提交前必须留住内容;系数 3/5 就是给「峰值 + 缓冲 + 并行」留的余量
CPU
有大事务时 ≈ 内存(GB) ÷ 2(按核数)
内存需求被拉高时,解析与入库的并发也要跟上,否则内存白给
KUFL 磁盘
= 服务数 × 日增量 × 保留天数(≥ 7 天)
中间文件是「可重放」的物理基础;删早了,断点就断了

举个算例:最大事务 5 GB、日增 100 GB、KUFL 保留 7 天,最低配置是每服务 8 核 / 15 GB 内存 / 700 GB 磁盘,推荐配置是 13 核 / 25 GB / 1.5 TB。这些数字比「建议 8C16G」这种拍脑袋的说法有用得多——因为它可以复核。

三段定界:先搞清楚慢在哪,再谈调参

性能问题最大的浪费,是在错误的地方调参数。KFS 给了一套非常工程化的定界方法: 把链路拆成解析、传输、入库三段,逐段量出「处理一份 KUFL 需要多久」,再和「业务生成这份数据用了多久」比。


Image
图 7|三段定界法

图 7|三段定界。这套方法最妙的地方是它只用了一条命令和一次计时:用 kufl list -last 判断解析是否落后,用 du 算传输速率,用 fsrepctl wait -applied 量入库耗时。不需要压测工具,也能在客户现场 20 分钟内给出结论。

定界之后才是调参:

  1. 加大缓冲:replicator.global.buffer.size 从默认 10 提到 100,减少目标库 commit 次数。几乎零成本。
  2. 增加入库通道:channels=4 + 内存队列 + 分区器。前提是「事务之间没有顺序依赖」,否则会踩冲突。
  3. 拆成多个服务:一个服务只同步一组表,彼此完全独立。这一招不仅加通道,还能让每个服务用更小的批量、更高的 PBE 效率。
  4. 组合:M 个服务 × 每个服务 N 个通道。到这一步基本是「用资源换吞吐」了。

源端解析慢的情况另有一套:大事务导致解析卡住 → 调 minRowsPerBlock 强制切片;业务量太大 → 源端多服务按表拆分;源库访问慢 → 把断点改存文件(dataSource=file_metadata)或开启元信息缓存(cacheMeta=true,仅 Oracle Redo 支持)。

一份可以直接抄的配置

下面这份 flysync.ini 是源端(Oracle → KES)的骨架,注释里标了每个参数「为什么在这里」:

# ---- 全局:安装路径与 RMI 端口 ----
[defaults]
install-directory=/home/flysync/kfs
rmi-port=11000

# ---- 源端服务 ----
[ora2kes]
role=master
master=node-a
members=node-a
kufl-port=3112
replication-host=10.0.0.11
replication-port=1521
replication-user=FLYSYNC
replication-password=******
datasource-type=oracle
oracle-extractor-method=logminer        # 换成 redo 可支持 LOB/DDL,但需同机
datasource-oracle-service=ORCL

# 只同步这两个模式;注意心跳表所在模式必须显式包含
property=replicator.extractor.dbms.tablePatterns=APP.*,FLYSYNC.*

# 大事务切片:每 1000 行一片,防止单事务撑爆内存
property=replicator.extractor.dbms.minRowsPerBlock=1000

# 取前项列,供目标端冲突裁决(双轨/双活必备)
property=replicator.extractor.dbms.getBeforeCols=true

# 默认不同步 DDL;需要同步时改用 ddlSupport 过滤器
svc-extractor-filters=dropstatementdata

目标端则是另一套逻辑——这里的每一个参数,都对应前面讲的某个「硬问题」:

# ---- 目标端:并行入库 + 分区 + 冲突裁决 ----
[kes-slave]
role=slave
master=node-a
datasource-type=kingbase8

# 1) 四个通道,事务按 shard 固定到通道(有外键必须用 ShardList)
svc_parallelization_type=memory
channels=4
property=replicator.store.parallel-queue.partitionerClass=\
  com.kingbase.flysync.replicator.storage.parallel.ShardListPartitioner
property=replicator.store.parallel.partitioner.shardListFile=/home/kfs/conf/shard.list

# 2) shard 值从 schema 改成表名,粒度才够细
svc-remote-filters=shardbytable,ddlSupport

# 3) 冲突裁决(DML 冲突按 5 类分别处理)
property=replicator.applier.dbms.conflictResolve=true
property=replicator.applier.dbms.beforeColsFile=\
  /home/kfs/support/filters-config/conflictResolveRule.json

# 4) 批量与缓冲:先给足,再按定界结果回调
property=replicator.applier.dbms.maxRowBatchSize=1000
property=replicator.global.buffer.size=100

如果只让我留一条排障命令,我会留这个——它把每个阶段、每个通道的耗时一次摊开:

$ fsrepctl -service kes-slave perf
Stage          | Seqno | Latency | Events | Extraction | Applying | Total
remote-to-kufl |   114 |  0.071s |    102 |    32.900s |   0.035s | 32.937s
kufl-to-q      |   114 |  0.105s |    114 |    32.907s |   0.061s | 32.970s
q-to-dbms      |    84 |  0.098s |     51 |    32.862s |   0.050s | 32.914s
q-to-dbms1     |   114 |  0.108s |     43 |    32.928s |   0.046s | 32.975s
q-to-dbms2     |    14 | 13.873s |      3 |    29.861s |   0.019s | 29.881s
q-to-dbms3     |    35 |  0.068s |     23 |    32.792s |   0.033s | 32.825s

看第四列 Events 与 Latency:q-to-dbms2 只处理了 3 个事件,却积压了 13.8 秒。这就是典型的通道倾斜——要么分区规则把热点表都指到了同一个通道,要么那条通道上有慢 SQL。不用猜,数字直接告诉你。

总结

不同身份的读者, 今天能从这篇文章带走啥?

  • 刚入门的 DBA:先记住两件事——seqno 是这条链路的唯一时间坐标,KUFL 是它的物理载体。所有「同步卡住了」的排查,都从 kufl list -last 看 TIME 是否前进开始。
  • 实施工程师:把三段定界法背下来,再记住三条顺序约束——先搬迁后拆通道、单通道可升多通道但不可逆、改 ini 必须 stop → fspm update → start。这三条能省掉你一半的返工。
  • 架构师:先定「抽取方式」,它同时决定了部署位置、能力边界(LOB/DDL)和网络预算。三个约束是一体的,不能分开拍板。
  • 资深专家:把「并行、冲突、校验」当成三个正交问题分别调。它们的耦合点只有一个:事务粒度。粒度选错,三个方向怎么调都是白费。

最后说一句我自己的判断。

看一个数据同步产品,我通常不看它支持多少种数据库,而看它在边界条件下的表现:大事务怎么办、断点怎么存、并行怎么保序、没有回包怎么传、一致怎么证明。这五个问题,是没有捷径的——每一个都要用具体的技术手段去换。

KFS 在这五个问题上的答案,指向同一种设计趣味: 把不可靠的部分(网络、对端、进程)都推到边界之外,把可靠性收敛到一个可以被持久化、被编号、被校验的中间层。KUFL 就是那个中间层。理解了这一点,你再看它那些参数,就不会觉得是零散的功能开关,而是一套自洽的工程取舍。