缓存与数据库一致性终极指南:从入门到放弃?不,到精通!下

简介: 本文探讨缓存一致性难题,从延时双删到重试机制,分析同步重试、异步重试、消息队列补偿及Binlog监听(Canal)等方案,结合优缺点与适用场景,最终提出根据业务需求权衡一致性与性能,选择合适策略。

上篇文章提到了延时双删仍然会存在删除缓存失败的情况。需要通过重试机制来保障删除缓存的成功率


前情回顾

上回说的小树做了一个缓存的功能,可是出现了数据不一致的情况,经过老大哥的指教,先是想到了旁路缓存的思路,后面还是会存在数据不一致的问题,经过几番优化,又找到了延时双删的策略来减少并发时出现的问题。可是仍然没有解决删除缓存时可能失败的问题,只是提到了可以考虑使用失败重试机制

失败重试的方案

老大哥跟小树说:
失败重试的方法一般分为两类

  • 同步重试
  • 异步重试

同步重试的实现就很简单了,直接在程序中实现,失败后再次删除,达到一定次数后停止重试。缺点也很明显,并发高的时候对接口性能很大。
异步的实现方式就很多了:

  1. 通过线程实现
    每次新增一个线程来进行重试操作。高并发下容易创建太多线程,会出现OOM问题;
    当然你可以通过线程池来管理线程,这样可以避免OOM问题
    但通过线程来进行重试的话,无法保留重试的记录,如果服务器重启,尚未重试成功的数据就会丢失
  2. 重试内容写入表中,通过定时任务执行
  3. 将重试的内容写入消息中间件中,通过消费消息来删除缓存,由消息中间件来保证消息的可靠消费

小树听了上面的三种方式说:第一种和第二种方法太简单了。你还是给我讲讲消息队列的方法吧

消息队列补偿

通过异步解耦,确保缓存删除操作最终成功。

实现步骤

322f7e932e60d4eabf45eb0cda1da5dd_MD5.jpeg

  1. 用户操作数据后,程序先更新数据库,然后删除缓存,成功直接返回,失败则放入消息中间件中
  2. 消费者检测到有消息开始消息消息,执行删除缓存操作,成功后删除这条消息,失败则进行重试,重试达到一定次数后,可根据业务做出处理如写入表中或者直接抛出错误

    优缺点

优点 缺点 适用场景
高可靠性 系统复杂度高 分布式系统
解耦业务 消息可能堆积 订单/支付系统

选型建议

  • 低并发场景:用Redis Stream实现轻量级队列
  • 高并发场景:RocketMQ + 死信队列监控

小树听了上面几种方案,又提问道:如果选上面几种方案的话,我需要改造原来的代码逻辑,有没有不需要改动原来代码就可以完成删除缓存的操作

方案四:Binlog监听(Canal)

通过数据库日志驱动缓存更新,实现业务零侵入。

Canal部署架构

27d8115546fc8f9d3e803fc0cfb7ac58_MD5.jpeg

具体方案如下:

  1. 程序只需要更新数据库就好了,其他的操作完全不关心。
  2. 中间件会伪装为MySQL 的从库,同步订阅binlog日志来获取变更的数据。
  3. binlog订阅者获取变更的数据,然后删除缓存。

优势

  • 完全解耦业务代码
  • 实时性达到毫秒级

    优缺点

优点 缺点 适用场景
业务零侵入 部署复杂 微服务架构
毫秒级同步 需处理DDL事件 数据异构同步

适用场景

  • 微服务架构中多个服务共享缓存
  • 数据异构场景(如ES索引同步)

小树听了总结道:上面这些方案我都理解了,但这些方案好像都只是尽量避免缓存不一致的情况,并没有真正的实现读缓存的时候一定和数据库保持一致
老大哥点了点头,说:是的,数据库追求数据的准确性和持久性,而缓存则追求极致的响应速度。大部分应用缓存的场景都是可以允许短暂的数据不一致的情况的,也就是最终一致性。如果追求强一致性,不可避免的就要牺牲一部分的响应性能。

常见的方案就是加锁,加锁方式又分为乐观锁和悲伤锁两种,这两个概念之前的文章有介绍过,这里简单总结一下各自的特点,就不详细介绍了

乐观锁:版本号控制

通过数据版本校验,实现精准更新。

额外成本

  • 存储开销增加5%-10%
  • 所有读写操作需校验版本号

回报

  • 缓存命中率提升40%
  • 彻底解决并发更新导致的脏读

终极大招:分布式锁

通过互斥锁确保同一时刻只有一个写操作。

性能数据

  • 吞吐量下降60%(实测)
  • 平均响应时间从20ms → 80ms

使用铁律

  • 仅用于库存扣减等核心场景
  • 必须设置锁超时时间!

最后抉择:一表帮你选型

场景特征 推荐方案 一致性强度 性能影响
读多写少 Cache-Aside 最终一致 无影响
高并发写入 双删+消息队列 最终一致+ 中等
金融/交易场景 分布式锁+版本号 强一致

选择方案时,记得问自己一句你的业务真的需要强一致性吗?

相关文章
|
2月前
|
人工智能 Java API
Java AI智能体实战:使用LangChain4j构建能使用工具的AI助手
随着AI技术的发展,AI智能体(Agent)能够通过使用工具来执行复杂任务,从而大幅扩展其能力边界。本文介绍如何在Java中使用LangChain4j框架构建一个能够使用外部工具的AI智能体。我们将通过一个具体示例——一个能获取天气信息和执行数学计算的AI助手,详细讲解如何定义工具、创建智能体并处理执行流程。本文包含完整的代码示例和架构说明,帮助Java开发者快速上手AI智能体的开发。
862 8
|
2月前
|
自然语言处理 前端开发 测试技术
使用 Playwright MCP 实现 UI 自动化测试
本文介绍如何结合Playwright与MCP协议实现智能化UI自动化测试。通过自然语言指令控制浏览器,降低技术门槛,提升效率,并涵盖环境搭建、核心功能、实战案例及最佳实践,展现对话式自动化的未来趋势。
|
2月前
|
安全 NoSQL Java
SpringBoot接口安全:限流、重放攻击、签名机制分析
本文介绍如何在Spring Boot中实现API安全机制,涵盖签名验证、防重放攻击和限流三大核心。通过自定义注解与拦截器,结合Redis,构建轻量级、可扩展的安全防护方案,适用于B2B接口与系统集成。
445 3
|
2月前
|
消息中间件 canal 缓存
缓存与数据库一致性终极指南:从入门到放弃?不,到精通!上
凌晨被投诉惊醒?缓存与数据库不一致是常见难题。本文详解五大解决方案:旁路缓存、双删策略、消息队列补偿、Binlog监听与版本号控制,结合场景分析一致性、性能与复杂度的权衡,助你选型不踩坑。
|
7天前
|
人工智能 数据可视化 测试技术
提升测试效率5倍!Dify驱动的可视化工作流实现自动化测试“开箱即用”
本文介绍如何利用Dify可视化工作流快速构建自动化测试体系,涵盖用例生成、API测试和UI测试等核心场景。通过拖拽式设计降低技术门槛,显著提升测试效率与覆盖率,助力团队实现质量保障的智能化转型。
|
2月前
|
IDE Java 关系型数据库
Java 初学者学习路线(含代码示例)
本教程为Java初学者设计,涵盖基础语法、面向对象、集合、异常处理、文件操作、多线程、JDBC、Servlet及MyBatis等内容,每阶段配核心代码示例,强调动手实践,助你循序渐进掌握Java编程。
326 3
|
SQL 存储 OLAP
阿里CCO基于Hologres的亿级明细BI探索分析实践
阿里CCO基于Hologres的亿级明细BI探索分析实践。
1655 0
阿里CCO基于Hologres的亿级明细BI探索分析实践
|
2月前
|
数据处理 Python Perl
仅包含外推轨道元数据的文件,可通过 SDP 工具包读取,二进制格式
AM1EPHNE为Terra卫星近实时外推星历数据,原生二进制格式,供SDP工具读取。文件名含时间、版本及生产信息,适用于精确轨道计算与遥感数据处理。(238字)
165 3
|
2月前
|
JSON 缓存 测试技术
程序出错瞎找?教你写“会说话”的错误日志,秒定位原因
错误日志是排查问题的“导航地图”。本文详解错误三大来源:参数非法、交互故障、逻辑疏漏,并分享写好日志的6大原则——完整、具体、直接、集成经验、格式统一、突出关键字,助你快速定位问题,提升系统可维护性。
232 0
|
负载均衡 监控 Dubbo
Dubbo 原理和机制详解(非常全面)
本文详细解析了 Dubbo 的核心功能、组件、架构设计及调用流程,涵盖远程方法调用、智能容错、负载均衡、服务注册与发现等内容。欢迎留言交流。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
Dubbo 原理和机制详解(非常全面)