一、背景与动机:为什么需要通过 Binlog 同步缓存?
在现代分布式系统中,缓存一致性 是绕不开的核心问题。传统方案如 Cache-Aside(缓存分离) 虽然简单,但在高并发场景下存在明显缺陷:
- 双写不一致:先更新 DB 再删缓存,若删除失败,缓存将长期脏读。
- 并发竞争:请求 A 更新 DB 后缓存未删,请求 B 读取旧缓存并回填,导致"旧值覆盖新值"。
- 业务侵入性强:每个写操作都要手动处理缓存逻辑,增加代码复杂度。
而基于 MySQL binlog 的异步同步方案(如 Canal + Redis)则能实现:
- 解耦业务逻辑:应用只需操作数据库,缓存由独立服务自动维护。
- 最终一致性保障:通过可靠消息队列或重试机制,确保数据最终一致。
- 高吞吐低延迟:binlog 是 MySQL 原生日志,解析效率高,适合海量变更场景。
二、Canal 工作原理详解
Canal 是阿里巴巴开源的 MySQL binlog 增量订阅 & 消费组件,其核心思想是 伪装成 MySQL Slave,通过标准 binlog 协议拉取数据。
核心组件
| 组件 | 功能 |
|---|---|
| Canal Server | 模拟 MySQL Slave,连接主库拉取 binlog |
| EventParser | 解析原始 binlog 为结构化事件(INSERT/UPDATE/DELETE) |
| EventSink | 将解析后的事件按事务聚合 |
| EventStore | 存储事件队列,支持内存或 Kafka/RocketMQ |
| Canal Client | 消费端,订阅事件并执行业务逻辑(如更新 Redis) |
工作流程(Mermaid)

注意:MySQL 必须设置
binlog_format=ROW,否则无法获取完整行变更。
三、Redis 与 Canal 集成方案
1. 数据流转路径
- 应用写入 MySQL(如
UPDATE users SET name='Alice' WHERE id=1001) - MySQL 写入 binlog
- Canal Server 拉取并解析为结构化事件
- Canal Client 接收事件,提取表名、主键、变更字段
- 根据预设规则生成 Redis Key(如
user:1001),执行对应命令
Key 设计规范
- 命名空间清晰 :
{业务}:{实体}:{ID}→order:detail:12345 - 避免大 Key:单个 Hash 不超过 5000 字段
- 设置 TTL :防止冷数据长期驻留(如
EXPIRE user:1001 3600)
2. 一致性保障策略
| 策略 | 说明 | 适用场景 |
|---|---|---|
| 延迟双删 | 先删缓存 → 更新 DB → 延迟 N 秒再删缓存 | 强一致性要求不高 |
| 版本号/时间戳 | 在 DB 中增加 version 字段,缓存携带版本,写入时校验 |
高并发写冲突多 |
| Binlog 顺序消费 | 确保同一 Key 的变更按序处理(如 Kafka 分区按主键哈希) | 严格最终一致 |
推荐:结合 版本号 + 顺序消费,可应对 99% 的生产场景。
四、常用 Redis 命令在同步场景中的应用
| 场景 | Redis 命令 | 示例 |
|---|---|---|
| 更新对象 | HSET key field value [field value ...] |
HSET user:1001 name Alice age 30 |
| 删除缓存 | DEL key |
DEL user:1001 |
| 设置过期 | EXPIRE key seconds |
EXPIRE user:1001 3600 |
| 发布通知 | PUBLISH channel message |
PUBLISH cache-invalidate user:1001 |
| 批量操作 | PIPELINE 或 MULTI/EXEC |
减少网络往返 |
提示:对于嵌套对象,建议使用 Hash 而非 JSON 字符串,便于部分更新。
五、典型应用场景分析
1. 电商商品缓存
- 需求:商品信息频繁读取,偶尔更新
- 方案 :Canal 监听
product表,更新product:{id}Hash - 优势:避免每次查询穿透 DB,QPS 提升 10 倍+
2. 用户会话同步
- 需求:用户登录态跨服务共享
- 方案 :监听
user_session表,写入session:{token},TTL=2h - 注意:需处理登出时的主动删除
3. 订单状态机
- 需求:订单状态变更需实时通知前端
- 方案 :更新 Redis 后
PUBLISH order:update {orderId} - 扩展:结合 WebSocket 实现实时推送
六、性能与可靠性考量
1. 避免三大缓存问题
| 问题 | 解决方案 |
|---|---|
| 缓存雪崩 | 热点 Key 设置随机 TTL;部署多级缓存 |
| 缓存穿透 | 布隆过滤器拦截无效 ID;空值缓存(短 TTL) |
| 缓存击穿 | 热点 Key 使用互斥锁(Redis SETNX) |
2. Canal 高可用部署
- Server 集群:基于 ZooKeeper 实现主备切换
- Client 容错:记录消费位点(如写入 MySQL 或 Redis),失败可重放
- 监控告警:监控 lag(binlog 位点延迟)、错误率、堆积量
七、高频面试题
Q1:Canal 如何保证不丢 binlog 事件?
答:Canal Client 消费成功后,会向 Server ACK 当前位点。Server 持久化该位点(默认内存,可配 ZooKeeper)。若 Client 重启,从最后 ACK 位置继续拉取。
Q2:如何解决 Canal 同步延迟导致的缓存不一致?
答:1)业务层读取时加版本号校验;2)对强一致场景,读 DB 后强制刷新缓存;3)监控延迟指标,超阈值告警。
Q3:为什么不用 MySQL UDF 或 Trigger 直接写 Redis?
答:UDF/Trigger 会阻塞 DB 主流程,影响写性能,且难以维护。Binlog 方案完全异步,对 DB 无侵入。
Q4:Canal 能监听分库分表吗?
答 :可以。Canal 支持配置多个 instance,每个 instance 监听一个 DB。也可通过正则匹配多表(如
.*\.order_.*)。
Q5:Redis 宕机期间 Canal 如何处理?
答:Client 应具备重试机制(指数退避),并将失败事件暂存(如本地文件或 Kafka)。待 Redis 恢复后重放。