2026/4/11 18:15:17
网站建设
项目流程
网站为什么做黄词骗流量,aspcms建站,网站移动端适配怎么做,弄个网站需要多少钱写在开头#xff1a; 我在公众号发了一篇《先更新数据库还是先删除缓存#xff1f;》的文章#xff0c;里面和大家聊到了 binlog 兜底方案的实现思路。 文章发出后#xff0c;有细心的小伙伴后台私信我#xff0c;指出方案里漏掉了先删除缓存这个关键操作。 这个反馈非常精…写在开头我在公众号发了一篇《先更新数据库还是先删除缓存》的文章里面和大家聊到了 binlog 兜底方案的实现思路。文章发出后有细心的小伙伴后台私信我指出方案里漏掉了先删除缓存这个关键操作。这个反馈非常精准缓存操作的顺序直接决定了用户是否能立刻读到新数据。 所以今天我特意整理了优化后的最终完整版方案补全了“先删缓存”的逻辑并增加了一个轻量级的降级方案希望能给被这个问题困扰的朋友一个生产级的标准参考。前天跟一个老同学喝茶他最近在给阿里某 P7 岗位招人。 他说面了十几个“五年经验”的 Java 开发一问到“如何保证 Redis 和 MySQL 的数据一致性”90% 的人都像背书一样回答 “用延时双删策略先删缓存再更数据库休眠 1 秒再删缓存。”老同学问“为什么要休眠 1 秒这 1 秒是怎么算出来的如果数据库主从同步延迟了 1.5 秒你这第 1 秒删了个寂寞难道你要休眠 2 秒那接口响应时间RT还要不要了” 对方直接哑火。说实话“延时双删”在低并发的小系统里能用但在大厂高并发场景下它就是个“缝合怪”方案既拖慢了性能阻塞 Sleep又无法保证 100% 的一致性不可靠的 Sleep 时间。今天咱们就撕开这层遮羞布看看大厂真正的“高可靠最终一致性”是怎么做的。一、 为什么 “延时双删” 在高并发场景下不适用为了解决“更新数据库期间旧数据被回填到缓存”的问题有人发明了“延时双删”先删缓存 - Update DB - Sleep(N) - 再删缓存这个方案在生产环境有两个致命硬伤硬伤 1Sleep 多少秒是个玄学你是 Sleep 500ms 还是 1s这个时间必须大于“主从同步延迟 业务查询耗时”。 但在大促流量洪峰下MySQL 主从延迟可能瞬间飙升到 3 秒甚至更久。你怎么保证你的 Sleep 时间一定够只要 Sleep 结束时从库数据还没同步过来别的线程读到的依然是旧数据并再次回填到缓存。硬伤 2吞吐量自杀你可是高并发系统啊为了数据一致性强行让业务线程Thread.sleep几百毫秒 这意味着你的接口响应时间RT直接增加了几百毫秒Tomcat 线程池瞬间被占满吞吐量QPS腰斩。在秒杀场景下这种代码就是给 CPU 递刀子。二、 生产级的标准答案Cache Aside Binlog 异步兜底在大厂的核心链路我们追求的是“低延迟的最终一致性”。 既然“第二次删除”是为了防止脏数据回填那为什么非要在业务线程里傻等把这个“等待”和“删除”的动作剥离出来交给异步组件去做才是正解。真正的架构方案App 侧保障实时性先删除缓存再更新数据库。注意保留“先删缓存”是为了让后续的读请求直接打到 DB 拿最新数据防止用户看到旧值。防坑指南对于热点 Key删除缓存后可能会引发“缓存击穿”导致 DB 压力骤增。建议配合分布式锁或逻辑过期策略来保护 DB而不是在业务代码里搞复杂的“回写旧值”。MySQL 侧数据变更产生 Binlog。中间件侧保障最终一致性Canal 监听 Binlog - 投递到 MQ - 消费者收到消息。Consumer 侧兜底清理消费者收到消息后执行第二次删除 Redis。这套方案是如何降维打击“双删”的解耦彻底去掉了 Sleep业务线程执行完Update DB直接返回成功不需要留下来陪跑。接口性能拉满。可靠性MQ 重试机制如果 Redis 挂了或者删除失败MQ 的 ACK 机制会保证消息不断重试直到删除成功为止。而“双删”方案里如果第二次删除失败了那缓存里永远是脏数据。解决主从延迟自适应MQ 的消息传输本身就有天然的“延时”。如果担心主从延迟极高可以在 Consumer 端配置“消费延迟”比如 RocketMQ 的 Level 3或者让 Consumer 查不到数据再删。这比在业务代码里硬写Sleep(1000)优雅一万倍。补充兜底策略针对 Canal 宕机、MQ 消息堆积等异常场景务必给缓存加上过期时间TTL。即使异步删除彻底失败脏数据也会在 TTL 到期后自动失效这是最后一道防线。三、 如果面试官问“Binlog 方案太重了怎么办”有些面试官会杠“引入 Canal 和 MQ系统复杂度太高了中小公司玩不起怎么办”这时候你要甩出“轻量级线程池延迟删除”方案如果不上一整套 Binlog 中间件可以在 Update DB 成功后往当前服务的“延迟消息队列”可以是基于内存的 DelayQueue或者 RocketMQ 的延迟消息扔一个任务。 任务内容{key: item_100, delay: 1000ms, retry: 3}。 由后台线程池去执行这个“第二次删除”。注意事项延迟队列虽然可以基于内存DelayQueue但在生产环境强烈建议使用RocketMQ/Kafka 延迟消息或Redisson 延迟队列。 因为基于内存的队列一旦服务重启任务就丢了会导致缓存永远删不掉。四、 面试标准答案模板建议背诵下次面试官再问“一致性”别上来就背双删试试这个高阶回答破题否定野路子“面试官在生产环境中我们一般不使用‘延时双删’。因为它强依赖Sleep时间不仅严重拖累接口吞吐量而且在数据库主从延迟抖动时依然无法保证一致性。”抛出方案亮肌肉“我们采用的是‘Cache Aside Pattern 的增强版’也就是先删缓存 Update DB 基于 Binlog 的异步兜底删除。 业务线程先删缓存保证数据的实时性然后更新 DB。 至于‘防止脏数据回填’的逻辑我们剥离给 Canal MQ 去做。Canal 监听 Binlog 变动投递给 MQ消费者负责进行‘第二次删除’。”升华价值讲收益“这样做有两个核心优势 第一是高性能业务线程完全不需要阻塞等待响应极快 第二是高可靠利用 MQ 的重试机制即使 Redis 抖动或删除失败也能通过重试保证缓存最终一定被清理真正实现了‘最终一致性’。当然该方案的核心是用架构复杂度换取高性能在中小系统如果不具备 Canal 条件我们也会降级为‘轻量级延迟消息’方案兼顾不同业务体量的需求。”写在最后架构设计的本质是权衡Trade-off。 “延时双删”是用性能换一致性而且还换得不彻底。 “Binlog 异步删”是用架构复杂度引入 MQ换取了高性能 高可靠。 作为 P7 架构师我们永远选择后者。https://mp.weixin.qq.com/s/2CcZZctJ2_kcokhMnNhY8A