打破困局:数据库与缓存不一致问题全解析

数据库和缓存不一致性的问题,如何解决?

回答

  • 为了保证 Redis 和 数据库 的一致性,肯定要缓存和数据库双写

  • 业内常见的 3 种方案:

    1. 先更新数据库,再删除缓存

    2. 延迟双删

      先删除缓存,

      再更新数据库,

      再删除一次缓存

    3. cache-aside

      更新数据库,基于 bin log 监听进行缓存删除

扩展

1、为什么要删缓存,而不是更新?
  • 优先选择删除缓存,而不是更新缓存。

  • 更新缓存的动作,相比于直接删除缓存。操作过程比较的复杂,而且,也容易出错

    更新缓存的例子:当我们需要通过缓存进行扣减库存的时候。

    1. 你可能需要从缓存中查出整个订单模型数据,

    2. 把他进行反序列化之后,再解析出其中的库存字段,

    3. 把他修改掉,然后再序列化,

    4. 最后,再更新到缓存中可以看到

  • 缓存删除 带来的一个小问题

    缓存删除会带来一次 cache miss

    这种 cache miss 在某种程度上,可能会导致缓存击穿

    通过加锁的方式,比较方便的解决缓存击穿的问题的。

2、先写数据库,还是先删缓存?
  1. 先删缓存,再写数据库

    • 优点:先删除缓存成功了,但是,第二步更新数据库失败了,这种情况是可以接受的。

    • 缺点:这种方式,会无形中放大 "读写并发",导致的数据不一致的问题。

      对于一个读线程来说,虽然,不会写数据库,但是,会更新缓存的。

      数据不一致的问题,示例:

      假如一个读线程,在读缓存的时候没查到值,他就会去数据库中查询。

      如果,在查询到结果之后,更新缓存之前,数据库被更新了。

      但是,这个读线程是完全不知道的。

      那么,就导致最终缓存会被重新用一个"旧值"覆盖掉。

      这样,就导致了缓存和数据库的不一致的现象

  2. 先写数据库,再删缓存

    • 好处1:缓存删除失败的概率还是比较低的。
    • 好处2:数据库是作为持久层存储的。先更新数据库,就可以保证数据的可靠性和一致性。
    • 问题:先写数据库,后删除缓存,如果,第二步失败了。会导致数据库中的数据已经更新,缓存还是旧数据。最终,导致数据不一致。
  3. 延迟双删

3、如何选择?
  1. 业务量不大,并发不高的情况,可以选择**"先更新数据库,后删除缓存"**的方式,因为,这种方案更加简单。

    先操作数据库,后操作缓存。是一种比较典型的设计模式一 Cache Aside Pattern

    这种模式的主要方案就是,先写数据库,后删缓存。而且,缓存的删除是可以在旁路异步执行的。

    这种模式的优点,他可以解决 "写写并发"导致的数据不一致问题 ,并且,可以大大降低"读写并发"的问题。

    所以,这也是Facebook比较推崇的一种模式。

  2. 业务量比较大,并发度很高的话 ,那么建议选择 "先删除缓存,再更数据库" 。因为,这种方式在引入延退双删、分布式锁 等机制,会使得整个方案会更加趋近于完美,带来的并发问题更少 。当然,也会更复杂

4、方案再优化
  • Cache Aside Pattern 这种模式中,我们可以异步的在旁路处理缓存。
  • 实现方式:借助数据库的 bin log 或者 基于异步消息订阅的方式。
    1. 先操作数据库,数据库操作完,发一个异步消息出来。
    2. 然后,再由一个监听者在接到消息之后,异步的把缓存中的数据删除掉。
    3. 或者,借助数据库的 binlog,订阅到数据库变更之后,异步的清除缓存。
  • 再完美一点(按实际情况评估,是否需要此方案)
    1. 先删缓存
    2. 再更新数据
    3. 再监听binlog删除缓存
5、缓存更新的设计模式
  1. Read/Write Through Pattern

    • Read Through 模式

      是由缓存配置一个读模块,它知道如何将数据库中的数据写入缓存。

      在数据被请求的时候,如果未命中,则将数据从数据库载入缓存。

    • Write Through 模式

      缓存配置一个写模块,它知道如何将数据写入数据库。

      当应用要写入数据时,缓存会先存储数据,并调用写模块将数据写入数据库

    • 这两种模式下,不需要应用自己去操作数据库,缓存自己就把活干完了。

  2. Write Behind Caching Pattern

    • 在更新数据的时候,只更新缓存,而不更新数据库。然后,再异步的定时的,把缓存中的数据持久化到数据库中。
    • 优点/缺点:读写速度都很快,但是,会造成一定的数据丢失。
    • 适合场景:可以用在比如:统计文章的访问量、点赞等场景中。允许数据少量丢失,但是,速度要快。
6、没有银弹
  1. 任何的技术方案,都是一个权衡的过程。
  2. 没有一个"完美"的方案,只有"合适"的方案。
相关推荐
在京奋斗者35 分钟前
spring boot自动装配原理
java·spring boot·spring
明天不下雨(牛客同名)3 小时前
为什么 ThreadLocalMap 的 key 是弱引用 value是强引用
java·jvm·算法
多多*4 小时前
Java设计模式 简单工厂模式 工厂方法模式 抽象工厂模式 模版工厂模式 模式对比
java·linux·运维·服务器·stm32·单片机·嵌入式硬件
草捏子5 小时前
从CPU原理看:为什么你的代码会让CPU"原地爆炸"?
后端·cpu
嘟嘟MD5 小时前
程序员副业 | 2025年3月复盘
后端·创业
胡图蛋.5 小时前
Spring Boot 支持哪些日志框架?推荐和默认的日志框架是哪个?
java·spring boot·后端
无责任此方_修行中5 小时前
关于 Node.js 原生支持 TypeScript 的总结
后端·typescript·node.js
牛马baby6 小时前
Java高频面试之并发编程-01
java·开发语言·面试
小小大侠客6 小时前
将eclipse中的web项目导入idea
java·eclipse·intellij-idea
不再幻想,脚踏实地6 小时前
MySQL(一)
java·数据库·mysql