项目中添加了redis后数据一致性问题处理

数据一致性的根本原因是 缓存和数据库中的数据不同步,那么我们该如何让缓存和数据库中的数据库尽可能的即时同步?

常见的缓存更新策略

  • 内存淘汰(全自动)。利用Redis的内存淘汰机制实现缓存更新,Redis的内存淘汰机制是当Redis发现内存不足时,会根据一定的策略自动淘汰部分数据

    • 一致性:差

    • 维护成本:无

    • Redis中常见的淘汰策略:

      • noeviction(默认):当达到内存限制并且客户端尝试执行写入操作时,Redis 会返回错误信息,拒绝新数据的写入,保证数据完整性和一致性

      • allkeys-lru:从所有的键中选择最近最少使用(Least Recently Used,LRU)的数据进行淘汰。即优先淘汰最长时间未被访问的数据

      • allkeys-random:从所有的键中随机选择数据进行淘汰

      • volatile-lru:从设置了过期时间的键中选择最近最少使用的数据进行淘汰

      • volatile-random:从设置了过期时间的键中随机选择数据进行淘汰

      • volatile-ttl:从设置了过期时间的键中选择剩余生存时间(Time To Live,TTL)最短的数据进行淘汰

  • 超时剔除(半自动)。手动给缓存数据添加TTL,到期后Redis自动删除缓存,下次查询时更新缓存

    • 一致性:一般
    • 维护成本:低
  • 主动更新(手动)。手动编码实现缓存更新,在修改数据库的同时更新缓存

    • 双写方案:人工编码方式,缓存调用者在更新完数据库后再去更新缓存。使用困难,灵活度高。

      1. 读取: 当需要读取数据时,首先检查缓存是否存在该数据。如果缓存中存在,直接返回缓存中的数据。如果缓存中不存在,则从底层数据存储(如数据库)中获取数据,并将数据存储到缓存中,以便以后的读取操作可以更快地访问该数据。
      2. 写入:当进行数据写入操作时,首先更新底层数据存储中的数据。然后,根据具体情况,可以选择直接更新缓存中的数据(使缓存与底层数据存储保持同步),或者是简单地将缓存中与修改数据相关的条目标记为无效状态(缓存失效),以便下一次读取时重新加载最新数据

      使用双写方案需要考虑以下几个问题:

      • 是使用更新缓存模式还是使用删除缓存模式

        • 更新缓存模式: 每次更新数据库都更新缓存,无效写操作较多(不推荐使用

        • 删除缓存模式:更新数据时更新数据库并删除缓存,查询时更新缓存,无效写操作较少(推荐使用

          选择使用删除缓存模式,那么是先操作缓存还是先操作数据库?

          • 先操作数据库:先更新数据库,再删除缓存

            当线程1在查询缓存且未命中,此时线程1查询数据,查询完准备写入缓存时,由于没有加锁线程2乘虚而入,线程2在这期间对数据库进行了更新,此时线程1将旧数据返回了,出现了脏读,这个事件发生的概率很低,因为先是需要满足缓存未命中,且在写入缓存的那段事件内有一个线程进行更新操作,缓存的查询很快,这段空隙时间很小,所以出现脏读现象的概率也很低

            这种方式的不足之处:存在脏读现象,但概率较小

            选择先更新数据库,再删除缓存,那么 如何保证缓存与数据库的操作的原子性?

            • 对于单体系统1,将缓存与数据库操作放在同一个事务中(当前项目就是一个单体项目,所以选择这种方式)
            • 对于分布式系统2,利用TCC(Try-Confirm-Cancel)等分布式事务方案
    • 读写穿透方案: 将读取和写入操作首先在缓存中执行,然后再传播到数据存储

      • 读取穿透(Read Through):当进行读取请求时,首先检查缓存。如果所请求的数据在缓存中找到,直接返回数据。如果缓存中没有找到数据,则将请求转发给数据存储以获取数据。获取到的数据随后存储在缓存中,然后返回给调用者。

主动更新策略中三种方案的比较

  • 双写方案 和 读写穿透方案 在写入数据时都会直接更新缓存,以保持缓存和底层数据存储的一致性。而 写回方案 延迟了缓存的更新操作,将数据先放入缓存队列,然后再进行批量或异步写入。
  • 读写穿透方案 和 写回方案 相比,写回方案 具有更高的写入性能,因为它通过批量和异步操作减少了频繁的写入操作。但是 写回方案 带来了数据一致性的考虑,需要确保缓存和底层数据存储在某个时间点上保持一致,而 读写穿透方案 将数据库和缓存整合为一个服务,由服务来维护缓存与数据库的一致性,调用者无需关心数据一致性问题,降低了系统的可维护性,但是实现困难

主动更新策略中三种方案的应用场景

  • 双写方案 较适用于读多写少的场景,数据的一致性由应用程序主动管理
  • 读写穿透方案 适用于数据实时性要求较高、对一致性要求严格的场景
  • 写回方案 适用于追求写入性能的场景,对数据的实时性要求相对较低、可靠性也相对低

更新策略的应用场景

  • 对于低一致性需求,可以使用内存淘汰机制。例如店铺类型数据的查询缓存
  • 对于高一致性需求,可以采用主动更新策略,并以超时剔除作为兜底方案。例如店铺详情数据查询的缓存

缓存中常见的三种问题

缓存穿透

是指客户端请求的数据在缓存中和数据库中都不存在,这样缓存永远不会生效,这些请求都会打到数据库

常见解决缓存穿透的方案

  • 缓存空对象
  • 布隆过滤
    • 内存占用较少,没有多余key
    • 实现复杂,存在误判可能(有穿透风险),无法删除数据
  • 主动的解决方案:增强id的复杂度避免被猜测id规律做好数据的基础格式校验加强用户权限校验做好热点参数的限流

缓存雪崩

是指在同一时段大量的缓存key同时失效或者Redis服务宕机,导致大量请求到达数据库,带来巨大压力。

缓存雪崩常见的解决方案

  • 给不同的Key的TTL添加随机值
  • 利用Redis集群提高服务的可用性
  • 给缓存业务添加降级限流策略,比如快速失败机制,让请求尽可能打不到数据库上
  • 给业务添加多级缓存

缓存击穿

也叫热点Key问题,就是一个被高并发访问并且缓存重建业务较复杂的key突然失效了,无数的请求访问会在瞬间给数据库带来巨大的冲击。

缓存击穿的常见解决方案

  • 互斥锁(时间换空间)

    • 优点:内存占用小,一致性高,实现简单
    • 缺点:性能较低,容易出现死锁
  • 逻辑过期(空间换时间)

    • 优点:性能高
    • 缺点:内存占用较大,容易出现脏读

两者相比较,互斥锁更加易于实现,但是容易发生死锁,且锁导致并行变成串行,导致系统性能下降,逻辑过期实现起来相较复杂,且需要耗费额外的内存,但是通过开启子线程重建缓存,使原来的同步阻塞变成异步,提高系统的响应速度,但是容易出现脏读

posted @ 2025-05-18 17:01  小郑[努力版]  阅读(30)  评论(0)    收藏  举报