延迟双删之外:缓存一致性的几种取舍

举报
yd_275379215 发表于 2026/11/07 09:55:05 2026/11/07
【摘要】 "先更新数据库还是先更新缓存"能吵一个下午,实际上没有零成本的一致性方案,只有按业务容忍度选的折中。把我们在不同业务上用过的四种方案和它们的坑摆出来。方案一:CacheAside,读miss回源、写时删缓存。最普及的姿势,但"写DB后删缓存"的两步不是原子的:删缓存失败的瞬间,旧数据会在缓存里躺到下次过期。我们的兜底是删除失败进重试队列(发MQ延迟重投),顺手把缓存TTL控制在分钟级,最坏不...

"先更新数据库还是先更新缓存"能吵一个下午,实际上没有零成本的一致性方案,只有按业务容忍度选的折中。把我们在不同业务上用过的四种方案和它们的坑摆出来。

方案一:Cache Aside,读 miss 回源、写时删缓存。最普及的姿势,但"写 DB 后删缓存"的两步不是原子的:删缓存失败的瞬间,旧数据会在缓存里躺到下次过期。我们的兜底是删除失败进重试队列(发 MQ 延迟重投),顺手把缓存 TTL 控制在分钟级,最坏不一致窗口有上界。

方案二:延迟双删,针对"读写并发时旧值回填"的经典场景:A 改 DB 删缓存,B 在删除前读到 miss、用旧数据回填。解法是写完 DB 后延迟几百毫秒再删一次。实现要讲究:

public void updateProduct(Product p) {
    productMapper.update(p);           // 1. 更新 DB
    redis.delete(key("product", p.getId())); // 2. 第一删
    // 3. 延迟二删:清掉并发读回填的旧值
    delayQueue.offer(new DelTask(key, 500)); // 500ms 后执行
    // 也可以用 MQ 延迟消息,重启不丢任务
}

延迟的时长是玄学部分,要覆盖"一次读请求从 miss 到回填"的完整耗时(DB 慢查询时长加网络往返),我们取读 P99 加 200 毫秒。它的本质是用第二次删除换一致性,代价是延迟期间的脏读窗口依然存在。

方案三:订阅 binlog 异步删(Canal 加 MQ),把"删缓存"从业务代码里剥离,DB 变更后由下游统一处理。好处是业务零侵入、删除失败可重放,是我们主推的方案;代价是链路变长,秒级延迟。

方案四:强一致场景直接放弃缓存自愈,用分布式锁串行化读写,或者把"读缓存"降级为"读 DB 加短 TTL"。库存这类数字敏感的业务,我们就是这么干的——缓存的性能收益让位给正确性。

后来这套组合又进化了一次:binlog 订阅做主通道后,延迟双删降级为兜底(仅订阅链路延迟超阈值时触发),业务代码里的删除逻辑收敛到统一 SDK。一致性方案的演进方向是把复杂性从业务代码搬到基础设施,应用侧只保留幂等和兜底两件事。

选型树总结:允许秒级不一致,binlog 异步删加 TTL 兜底;并发回填频发,叠加延迟双删;要求最终一致且可审计,重试队列必须上;要求强一致,别难为缓存了,它不是干这个的。工程上没有免费的午餐,只有明码标价的取舍。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。