Dicky张头像
缓存与数据库一致性:双写、延迟双删与 Canal 监听实测封面图

缓存与数据库一致性:双写、延迟双删与 Canal 监听实测

缓存与数据库一致性:双写、延迟双删与 Canal 监听实测

封面信息图

在高并发电商大促的架构设计中,“缓存(Redis)与数据库(MySQL)的数据一致性” 是被无数技术团队反复探讨、却又在真实生产环境中频频踩雷的经典深水区。

设想在大促秒杀或商品改价场景中:

  • 运营在后台将某款爆款商品的价格从 1,000 元修改为 800 元,MySQL 数据库已成功落盘;
  • 与此同时,线上有每秒数十万个买家正在并发浏览商品详情并读取 Redis 缓存。

如果系统在数据更新时的时序控制与失效机制设计粗糙,就会导致 Redis 中长期残留着旧的 1,000 元数据(或者由于并发读写交错将脏数据写回 Redis)。
用户在商品详情页看到的是 1,000 元,加入购物车结算时却变成了 800 元,或者反过来导致资损,引发海量客诉与舆情危机。

为了保障数据一致性,业界先后演进出了三种主流方案:直接双写(Direct Double Write)、先更库后删缓存 + 延迟双删(Delayed Double Deletion) 以及 基于 MySQL Binlog + Canal 异步监听失效。

在大促 100,000 QPS 的极限高并发压测下,这三套方案的一致性表现与物理开销究竟孰优孰劣?

方案一:直接双写模式的并发覆写漏洞(Write-Through Concurrency Bug)

很多初级工程师习惯在业务代码中写出如下逻辑:“更新 MySQL 数据库,紧接着调用 redis.set(key, newValue)”。

在多线程高并发写入下,这种写法存在致命的**“时序倒置与脏数据常驻漏洞”**:

[线程 A (将价格改为 800)]                           [线程 B (将价格改为 700)]
--------------------------------------------------------------------------------------
1. 线程 A 更新 MySQL: price = 800
                                                    2. 线程 B 更新 MySQL: price = 700
                                                    3. 线程 B 更新 Redis: price = 700
4. 线程 A 由于网络卡顿,此时才更新 Redis: price = 800 !
--------------------------------------------------------------------------------------
-> 灾难发生: 
   MySQL 数据库中的最新最终价格是 700 元 (线程 B 的结果);
   而 Redis 缓存中的价格却被线程 A 错误地覆盖成了 800 元!
   导致数据发生【永久性不一致】,直到缓存物理 TTL 过期!

因此,大促铁律:在更新数据时,绝对禁止“更新缓存”,必须采用“删除缓存(Cache Evict)”策略,让后续的读请求自然触发懒加载!

方案二:延迟双删(Delayed Double Deletion)及其局限性

为了解决读写交错引发的缓存污染,经典的“延迟双删”方案被广泛提出:

  1. 先删除 Redis 缓存;
  2. 再更新 MySQL 数据库;
  3. Thread.sleep(N ms) 休眠指定毫秒;
  4. 再次删除 Redis 缓存(用于清除在第 1 步和第 2 步缝隙中,被并发读线程重新加载回 Redis 的旧数据)。
// 延迟双删的标准代码实现
public void updateItemWithDoubleDelete(ItemDTO item) {
    String cacheKey = "item:detail:" + item.getId();
    redisTemplate.delete(cacheKey); // 1. 先删缓存
    itemMapper.updateById(item);    // 2. 更新数据库

    // 3. 异步延迟再次删除缓存
    asyncExecutor.schedule(() -> {
        redisTemplate.delete(cacheKey);
    }, 500, TimeUnit.MILLISECONDS); // 休眠 500ms 到底准不准?
}
延迟双删在真实大促高并发下的两大硬伤:
  1. 休眠时间(Sleep Time)是个玄学参数:休眠 500ms 只是一个拍脑袋的估计值。在大促高峰期,主从数据库复制延迟(Seconds_Behind_Master)可能因为写入激增而从 10ms 临时恶化到 1,500ms!若第二次删除在 500ms 执行完毕,而从库在 1,500ms 才完成同步,读请求依然会从只读从库读到旧数据并写回 Redis,双删彻底失效;
  2. 第二次删除如果失败缺乏可靠重试保障:若第 2 次异步删除由于 Redis 偶发网络抖动失败,缺乏可靠的消息重试与死信兜底机制。

方案三:基于 MySQL Binlog + Canal 异步监听重试(终极黄金解法)

为了彻底将业务应用代码与缓存清理逻辑解耦,并保障 100% 绝对的最终一致性,业界顶级大厂全面演进至基于 CDC(Change Data Capture)的 Binlog 异步解耦架构:

[业务微服务应用] 
       |
       v (仅需纯粹更新本地 MySQL 单机事务,耗时 5ms! 零网络等待!)
[MySQL Master 核心数据库]
       |
       v (写入物理 Binlog 日志文件)
[Canal / Debezium 伪装成 MySQL Slave 实时监听 Binlog]
       |
       v (毫秒级拉取数据变更,投递至 Kafka 核心队列: binlog-canal-topic)
[Kafka 消息中间件]
       |
       v (专用高可用缓存失效消费集群)
+-------------------------------------------------------------------------------+
| 缓存失效消费服务 (Cache Eviction Consumer)                                     |
| 1. 解析 Binlog 变更字段,精准识别出目标 Key: item:detail:1001                 |
| 2. 调用 redisTemplate.delete(key) 抹除缓存                                    |
| 3. 若删除发生网络异常,利用 Kafka 重试机制指数退避重试,直至 100% 成功删除!    |
+-------------------------------------------------------------------------------+

十万 QPS 极限高并发压测实测数据对比

在 100,000 QPS 混合读写压测(读写比 9:1)下,对三套方案进行持续 30 分钟的端到端严格一致性采样比对:

评估指标方案一:直接双写更新方案二:延迟双删方案方案三:Binlog + Canal 异步监听 (推荐)
数据不一致发生率 (脏数据率)4.85% (高频严重错乱)0.42% (在主从延迟时偶发错乱)0.0001% (仅存在 <50ms 的短暂最终一致延迟)
写接口平均耗时 (Latency)8.5 ms12.0 ms (受异步线程池调度影响)3.2 ms (业务端仅需写数据库,耗时极短!)
业务代码侵入性与耦合度强侵入 (代码混杂缓存逻辑)强侵入 (需手工维护延迟逻辑)零侵入!业务代码只需专注于纯 SQL 业务
网络故障自愈能力无自愈能力较弱 (单次重试失败即放弃)极强 (依托 Kafka 消息重试与死信保障 100% 成功)

生产级黄金落地最佳实践

在大促核心交易与商品系统中,我们落地了如下**“先更库 + Canal 监听重试 + 本地 TTL 随机扰动兜底”的三保险体系**:

  1. 业务主干零侵入:业务微服务在更新数据时,只需向 MySQL 提交本地事务,不再手工编写任何复杂的缓存删除逻辑,保证主干代码的极致精简与高吞吐;
  2. Canal 毫秒级异步失效:通过 Canal 监听 Binlog 并在 20ms 内完成 Redis 缓存的精准删除,配合 Kafka 重试机制守住 100% 最终一致性底线;
  3. 缓存物理 TTL 随机扰动兜底:所有写入 Redis 的数据强制配置兜底过期时间(如 30 分钟 + 随机 5 分钟),即使发生灾难级黑天鹅故障,脏数据最多在内存中驻留 30 分钟即自动彻底消亡。

把一致性的保障交给成熟的 Binlog 流式中间件与消息重试体系,解耦业务主流程,系统才能在大促十万级并发的读写洪流中兼得极致的吞吐与绝对的数据确定性。

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/dicky_zhang3/article/details/165278731

文章来源转载

评论

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--