2. 主流CDC技术选型
做CDC选型,说白了就是选一种「偷看」数据库变更的方式。我这些年接触过的项目,基本逃不出三种路子:日志、轮询、触发器。每种都有它的脾气,选错了后面运维会很难受。
咱们一个一个来看。
2.1 基于日志的CDC(Debezium、Canal)
这是目前最主流的方式。原理很简单——数据库自己就有事务日志(比如MySQL的binlog、PostgreSQL的WAL),CDC工具直接去读这些日志,解析出数据变更。
我个人习惯把这种方式叫做「旁路监听」。它不侵入业务表,不写触发器,不碰业务代码。数据库怎么写日志,它就怎么读。
市面上两个代表项目:
- Debezium:基于Kafka Connect生态,支持MySQL、PostgreSQL、MongoDB、Oracle等。我在一个金融项目里用它同步核心交易库到数仓,跑了两年没出过大问题。
- Canal:阿里开源的MySQL binlog解析组件,专精MySQL。如果你只用MySQL,Canal更轻量,部署也简单。
举个例子,Debezium监听到一条INSERT,会吐出类似这样的JSON:
{
"op": "c", // c=create, u=update, d=delete
"ts_ms": 1700000000000,
"before": null,
"after": {
"id": 1001,
"name": "张三",
"balance": 5000.00
},
"source": {
"db": "bank",
"table": "accounts"
}
}
嗯,这里要注意——日志格式因数据库而异。MySQL的binlog有ROW、STATEMENT、MIXED三种格式,CDC必须用ROW格式才能拿到完整数据。我曾经接手过一个项目,源库配的是STATEMENT格式,Debezium死活解析不出具体字段值,折腾了两天才发现是配置问题。
2.2 基于轮询的CDC
轮询CDC,说白了就是「定时查表」。你在业务表上加一个时间戳字段或版本号字段,然后写个定时任务,每隔几秒或几分钟去查「最近有变化的数据」。
你想想看,这种方式有什么问题?
- 延迟高:轮询间隔决定了延迟下限,做不到实时。
- 无法捕获删除:行都没了,你查不到。
- 对源库有压力:频繁的SELECT会抢业务库的连接和IO。
但它也有适用场景。我记得在一个ERP系统集成项目里,源库是Oracle 11g,版本太老不支持日志解析,业务方又不让加触发器。最后我们只能妥协——在核心表上加了个 last_modified 字段,每30秒轮询一次。虽然延迟能接受,但每次大促期间轮询线程一多,数据库CPU就往上蹿。
一个简单的轮询SQL示例:
-- 假设表结构中有 last_modified 字段
SELECT * FROM orders
WHERE last_modified > #{lastCheckTime}
ORDER BY last_modified ASC
LIMIT 1000;
这里有个坑——时间精度问题。如果业务在同一毫秒内更新了多条记录,轮询可能漏掉一部分。我一般建议用自增ID + 时间戳组合来做断点标记,更稳妥。
2.3 基于触发器的CDC
触发器CDC,就是在源库的表上创建INSERT、UPDATE、DELETE触发器,把变更记录写入一张专门的日志表。然后CDC工具再去读这张日志表。
这种方式听起来很直接,对吧?但我在实际项目中吃过它的亏。
先说优点:
- 能捕获所有变更(包括删除和旧值)
- 不依赖数据库日志格式
- 兼容性广(任何支持触发器的数据库都能用)
再说缺点:
- 性能开销大:每次DML操作都要额外写一条日志,事务变重。我在一个日活千万的电商订单库上试过,加了触发器后写入吞吐下降了约15%。
- 维护成本高:表结构变更时,触发器也得同步更新。有一次DBA改了表字段类型,忘了更新触发器,结果日志表里全是NULL。
- 单点故障风险:如果日志表满了或者挂了,业务表的写入也会被阻塞。
一个典型的触发器示例(MySQL):
CREATE TRIGGER trg_orders_after_update
AFTER UPDATE ON orders
FOR EACH ROW
BEGIN
INSERT INTO cdc_orders_log(
operation, table_name, record_id,
old_data, new_data, create_time
) VALUES (
'UPDATE', 'orders', NEW.id,
JSON_OBJECT('status', OLD.status, 'amount', OLD.amount),
JSON_OBJECT('status', NEW.status, 'amount', NEW.amount),
NOW()
);
END;
2.4 三种方案对比
我把它们放在一起对比,你一眼就能看出差异:
| 对比维度 | 基于日志 | 基于轮询 | 基于触发器 |
|---|---|---|---|
| 延迟 | 毫秒级 | 秒~分钟级 | 毫秒级 |
| 源库侵入性 | 无 | 低(仅查询) | 高(写触发器) |
| 捕获删除 | ✅ 支持 | ❌ 不支持 | ✅ 支持 |
| 捕获旧值 | ✅ 支持 | ❌ 不支持 | ✅ 支持 |
| 对源库性能影响 | 极低 | 中等(取决于轮询频率) | 较高 |
| 维护复杂度 | 低 | 低 | 高 |
| 适用场景 | 生产环境、实时场景 | 老旧系统、非实时 | 小规模、无日志解析支持 |
2.5 选型决策逻辑
我一般按这个思路来选:
- 先看源库是否支持日志解析。MySQL 5.7+、PostgreSQL 9.4+、Oracle 12c+ 基本都支持。能用日志就别想别的。
- 再看实时性要求。秒级以内必须上日志CDC;分钟级可以接受的话,轮询也能凑合。
- 最后看运维能力。日志CDC需要维护Kafka或类似的消息中间件,团队如果没经验,小项目可以先用触发器过渡。
下面这张图能帮你快速定位:
总结一下:基于日志的CDC是首选,基于轮询的是备选,基于触发器的是最后的选择。选型时别只看功能列表,还要考虑你团队的运维能力和源库的实际情况。毕竟,再好的技术方案,落不了地也是白搭。