用 binlog 订阅(如 Canal)做缓存/数据同步的原理与坑是什么?
结论先行:Canal 把自己伪装成 MySQL 从库,消费主库 binlog 的增量事件,再把这些变更转发给缓存、搜索引擎、异构存储等下游,让“写完 DB 再同步其它存储”从业务代码里解耦出来。这是变更数据捕获(CDC)的一种典型落地,适合最终一致可接受、又不想侵入业务的场景。
一、整体链路
- 业务写主库并提交事务,变更以行事件形式写进 binlog(要求 row 格式);
- Canal 模拟 slave 向主库发起 dump 请求,持续拉取 binlog 并解析成 Insert/Update/Delete 事件;
- 事件被投递到 Canal 自带 MQ/adapter,或由客户端直连消费;
- 下游按需消费:删除/更新缓存、同步到 ES、异构表、数仓等,消费端自己做幂等。
二、为什么要 row 格式
- statement 格式只记录 SQL,遇到不确定函数、批量更新时回放结果可能不一致;row 格式记录每行变更前后的值,最稳定,也是增量解析的前提;
- 使用前提是主库开启:
binlog_format=ROW、binlog_row_image=FULL,并给 Canal 一个独立账号与不冲突的 server-id:
[mysqld]
server-id = 100 # 与其它从库/Canal 实例错开
binlog_format = ROW
binlog_row_image = FULL
三、工程要点与常见坑
- 消费必须幂等:事件可能重复投递或重启后重放,按主键/版本号去重,或让操作天然可重入(例如删缓存、按幂等键更新);
- 顺序性:同一行的变更要按顺序消费,通常单表单线程或按 key 哈希分区,避免“后写先到”造成旧值覆盖新值;
- 延迟量级:拉取、解析、投递、消费整条链路存在亚秒到秒级延迟,实时性要求更高的场景不能依赖它;
- 双写 vs CDC:业务双写侵入强、容易漏改一处;CDC 解耦干净,但要多维护一套 Canal 的高可用与位点(offset)续传;
- 别对全库无差别订阅,先按库表白名单过滤,超大表/敏感表单独评估;
- 下游失败要能“重放+对账”:记录消费位点,故障恢复后从位点续传,定期跑一次对账任务兜底。
四、适用场景速查
| 场景 | 是否推荐 CDC | 理由 |
|---|---|---|
| 缓存与 DB 最终一致 | 推荐 | 删缓存天然幂等,解耦业务 |
| 同步数据到 ES 做搜索 | 推荐 | 异构存储同步的标准姿势 |
| 实时性要求毫秒级 | 不推荐 | 链路延迟摆在那 |
| 需要事务性强一致 | 不推荐 | CDC 只保证最终一致 |
常见追问/记忆点
- 追问:Canal 挂了怎么办?答:位点续传 + 消费端幂等兜底,极端情况从备份时间点重放,再配对账任务收敛。
- 追问:和“业务里发 MQ 删缓存消息”有什么区别?答:CDC 由 DB 变更驱动,不依赖业务每条路径都发消息;两者可以叠加,CDC 兜底、MQ 提速。
- 记忆点:伪装从库读 binlog → 解析行事件 → 幂等消费;适用“最终一致 + 秒级延迟可接受”的下游。