4、变更捕获机制:数据库日志解析(CDC)、应用层钩子、定时比对快照、事件驱动架构
数据变更捕获,说白了就是回答一个问题:数据到底什么时候变了?
我做了这么多年数据治理,见过太多团队在「找变更」这件事上栽跟头。有的靠人工排查,有的靠业务方通知,结果呢?数据对不上,影响分析全白做。
这一章,我把四种主流的变更捕获机制掰开揉碎讲清楚。每种机制都有它的适用场景,也有它的坑。你想想看,选错了方案,后面所有的影响分析都是空中楼阁。
4.1 数据库日志解析(CDC)
CDC,全称 Change Data Capture,翻译过来就是「变更数据捕获」。它是最主流、最可靠的方式之一。
它的原理很简单:数据库在写入数据时,会记录事务日志(比如 MySQL 的 binlog、PostgreSQL 的 WAL、Oracle 的 Redo Log)。CDC 工具就盯着这些日志看,一旦有新日志产生,就解析出来,变成我们能理解的数据变更事件。
核心思路:不侵入业务代码,直接从数据库底层捞变更。对业务系统零影响。
我个人习惯用 Debezium + Kafka 这套组合。Debezium 负责读日志,Kafka 负责把变更事件流式传输出去。举个例子,MySQL 的 binlog 格式是这样的:
# at 12345678
#230101 10:00:00 server id 1 end_log_pos 12345999 CRC32 0x12345678
# Table: mydb.users
# Type: UPDATE
# Before: id=1001, name='张三', email='zhangsan@old.com'
# After: id=1001, name='张三', email='zhangsan@new.com'
Debezium 解析后,会输出一个 JSON 事件:
{
"op": "u",
"before": {"id": 1001, "name": "张三", "email": "zhangsan@old.com"},
"after": {"id": 1001, "name": "张三", "email": "zhangsan@new.com"},
"source": {"db": "mydb", "table": "users", "ts_ms": 1672531200000}
}
你看,变更前后的数据、变更时间、变更类型,全都有了。影响分析拿到这个,直接就能追溯。
避坑指南:我曾经在一个高并发项目里,直接让 CDC 工具去读主库的 binlog。结果日志解析压力太大,把主库 IO 打满了。后来我改成读从库的 binlog,问题才解决。记住:CDC 一定要连从库或备库,别碰主库。
CDC 的优点是实时性高、无侵入。但缺点也很明显:它只能捕获数据库层面的变更。如果业务代码在内存里改了数据,没写库,CDC 就抓不到。
4.2 应用层钩子
应用层钩子,就是在业务代码里埋点。每次数据变更,主动发一个通知出来。
这种方式很直接。比如你在 Java 里用 Spring 的 @EventListener,或者在 Python 里用装饰器。数据一改,事件就发出去了。
// 伪代码示例
public class UserService {
@EventListener
public void onUserUpdate(UserUpdateEvent event) {
// 把变更事件发送到消息队列
messageQueue.send(event);
}
}
我见过不少团队用这种方式。它的好处是灵活——你能捕获任何你想捕获的变更,包括业务语义层面的变更(比如「用户升级为 VIP」这种逻辑变更)。
但它的坏处也很要命:你得改业务代码。老系统不敢动?新系统忘了埋点?钩子一漏,影响分析就断了。
注意:应用层钩子依赖开发人员的纪律性。我曾经在一个项目里,团队有 30 多个微服务,每个服务都自己埋点。结果埋点格式不统一,有的发 JSON,有的发 XML,有的干脆只发个字符串。最后我们不得不写一个适配层来统一格式。所以我的建议是:如果要用钩子,必须制定统一的埋点规范,并且用代码生成工具强制落地。
4.3 定时比对快照
定时比对快照,是最「笨」但最通用的方法。它的逻辑很简单:每隔一段时间,把当前数据全量导出来,和上一次的快照做对比,找出差异。
你想想看,这种方式不需要读日志,不需要改代码,只要你能拿到数据,就能做。对于老系统、第三方系统、甚至 Excel 表格,它都适用。
具体实现上,我一般会这样做:
- 生成快照:把目标表的数据导出成文件(CSV、Parquet 都行),或者直接存到一张快照表里。
- 比对差异:用 SQL 的
FULL OUTER JOIN或者写个 MapReduce 任务,逐行比对。 - 输出变更:把新增、修改、删除的记录提取出来,形成变更事件。
举个例子,比对两张快照表的 SQL 大概长这样:
SELECT
COALESCE(old.id, new.id) AS id,
CASE
WHEN old.id IS NULL THEN 'INSERT'
WHEN new.id IS NULL THEN 'DELETE'
ELSE 'UPDATE'
END AS change_type,
old.*,
new.*
FROM snapshot_20240101 old
FULL OUTER JOIN snapshot_20240102 new
ON old.id = new.id
WHERE old.id IS NULL OR new.id IS NULL
OR old.name != new.name
OR old.email != new.email;
这种方式最大的问题是时效性差。你只能知道「两个时间点之间发生了什么变化」,但不知道「具体什么时候变的」。而且全量比对很耗资源,数据量一大,跑一次可能要几个小时。
适用场景:定时比对快照最适合做「离线影响分析」和「数据对账」。比如每天凌晨跑一次,看看昨天哪些数据变了,然后更新影响分析结果。实时性要求不高的场景,它反而是最稳妥的方案。
4.4 事件驱动架构
事件驱动架构,不是一种具体的捕获技术,而是一种架构模式。它的核心思想是:一切数据变更都是事件,事件驱动整个系统的流转。
在这种架构下,每个服务在修改数据时,都会发布一个领域事件。其他服务订阅这些事件,做出相应的反应。数据变更的捕获,天然就融入了系统设计里。
我参与过一个电商系统的重构,就是用事件驱动架构做的。用户下单、支付、发货,每一步都产生事件。影响分析系统直接订阅这些事件,实时更新数据血缘和影响范围。效果非常好。
事件驱动架构的关键组件包括:
- 事件总线:Kafka、RabbitMQ、Pulsar 等消息中间件,负责事件的传输和路由。
- 事件存储:把事件持久化,方便回溯和重放。我一般用 Kafka 自带的日志存储,或者单独存到对象存储里。
- 事件处理器:消费事件,执行具体的业务逻辑或影响分析计算。
我的经验:事件驱动架构虽然好,但引入的复杂度也不小。事件顺序、幂等性、死信处理,都是要提前想好的。我建议先从 CDC 入手,等系统成熟了,再逐步引入事件驱动架构。别一上来就搞大跃进。
4.5 四种机制对比
说了这么多,我把四种机制的核心差异整理成一张表,方便你选型时参考:
| 特性 | 数据库日志解析(CDC) | 应用层钩子 | 定时比对快照 | 事件驱动架构 |
|---|---|---|---|---|
| 实时性 | 高(秒级) | 高(毫秒级) | 低(分钟/小时级) | 高(取决于事件总线) |
| 侵入性 | 无 | 高(需改代码) | 低(只读数据) | 高(需重构架构) |
| 捕获范围 | 数据库变更 | 业务逻辑变更 | 全量数据 | 业务事件 |
| 资源消耗 | 中(日志解析) | 低 | 高(全量比对) | 中(事件存储) |
| 适用场景 | 实时数据同步、影响分析 | 业务语义变更捕获 | 离线对账、历史回溯 | 微服务、领域驱动设计 |
4.6 知识体系总览
下面这张图,把四种变更捕获机制的核心逻辑和关系画出来了。你可以把它当作本章的知识地图:
嗯,四种机制讲完了。你可能会问:「那我到底该用哪一种?」
我的建议是:别只选一种。实际项目中,往往是多种机制组合使用。比如用 CDC 做实时变更捕获,用定时快照做离线对账和校验,用事件驱动架构处理跨服务的业务事件。组合起来,才能覆盖所有变更场景。