Redis学习(二十):代码实战之缓存异常场景

缓存异常场景

应用redis缓存时,会发生如下问题:

  • 缓存和数据库中数据不一致
  • 缓存雪崩
  • 缓存击穿
  • 缓存穿透

1.缓存和数据库中数据不一致

数据一致性包含两种情况:

  • 第一种:缓存中有数据,且数据和数据库中一致
  • 第二种:缓存中没有数据,且数据库的数据是最新的

1.1.发生场景

根据缓存的三种类型,来详细讲解下不一致可能出现的情形

(1)读缓存

  • 新增操作:新增数据,数据会直接写入到数据库,不涉及缓存操作,因此不会出现不一致的情形,且数据符合一致性的第一种情形

  • 删改操作:既要更新数据库,也需要删除缓存中的数据。两个操作如果无法保住原子性,则会出现数据不一致的问题

    如下图:

    img

  • 并发场景下的数据不一致

    • 情况一:先删除缓存,再更新数据库

      写+读:线程A删除缓存后,由于网络延迟还没有更新到库,此时线程B读取数据发现缺失,从库读出再同步入缓存,此时A才写入库,造成了缓存存在数据,但是数据不一致的情形

      img

    • 情况二:先更新数据库值,再删除缓存

      写+读:线程A删完库和删除缓存之间,存在其他线程发出读请求,这个时候的读请求由于缓存还没删除,所以读到了值并返回,但是这个场景对业务影响较小

      img

    (2)读写缓存-直写场景

    针对这种缓存类型的数据不一致的问题,大致与读场景相同,其实二者的区别也就是写请求的处理,读写缓存针对写请求是同步缓存,而读缓存是删除缓存,因此读写缓存场景下,新增的不一致的类型包括:新增请求,并发下的写写(执行顺序导致)请求

    • 新增操作:新增数据库和新增缓存需要保证原子性,因此可以用重试解决

    • 并发场景下的数据不一致:在读缓存的两个情形上多了个写写类型

      • 情况一:先更新缓存,再更新数据库

        写+读:不会影响业务,更新了缓存之后,其余请求会直接读缓存,对业务无影响

        写+写:线程A与线程B同时写数据D,缓存中的写顺序是AB、数据库写顺序是BA,导致不一致

      • 情况二:先更新数据库值,再删除缓存

        写+读:有部分请求会读到缓存的老数据,但是对业务系统短暂影响

        写+写:与情况一的写+写情形一致

写+读场景下,如果缓存不存在怎么办?

可能的问题:假设写请求W,读请求R

时间WR
T1 读取缓存无数据,读取DB
T2更新DB
T3删除缓存/更新缓存
T4 写入读取的数据入缓存

虽然这种场景会出现问题,不过由于读取缓存+写入读取的数据入缓存的耗时比更新DB的耗时小的多,因此发生的概率极低,不过不排除R线程由于GC导致STW中断

1.2.如何解决数据不一致

(1)重试机制

通过引入消息队列或者代码中手写重试代码,如果重试之后仍然失败则抛错

img

(2)针对并发场景下的读缓存的

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

  • (写+读)延迟双删:线程A再更新完数据库之后,再sleep一会再删除缓存一次,具体sleep时间更新线程读数据+写缓存的时间(其实就是上述线程B的耗时)

先更新数据库,再更新缓存:

  • (写+读)对业务系统短暂影响,无需特殊处理

(3)针对并发场景下的读写缓存的

先更新缓存,再更新数据库:

  • (读+写)由于对业务的影响都不大,因此无需特殊处理
  • (写+写)写请求进来时,针对同一个资源的修改操作,先加分布式锁,这样同一时间只允许一个线程去更新数据库和缓存,没有拿到锁的线程把操作放入到队列中,延时处理

先更新数据库,再更新缓存

  • (读+写)由于对业务的影响都不大,因此无需特殊处理
  • (写+写)同上写+写

写+写其实就是更新覆盖的场景,上述解决措施采取的悲观锁,这里其实也可以采取乐观锁的方式

1.3.先更新库还是先更新缓存的取舍

先总结下表:

img

建议优先更新库再删除缓存,好处如下:

  • 先删除缓存再更新库,可能会导致数据缺失给数据库带来压力
  • 先删除缓存再删库解决办法的延迟双删的sleep时间不好把握

2.缓存雪崩

2.1.概念:

大量的请求无法在缓存中进行处理,进而请求向数据库,导致数据库压力激增

2.2.发生场景及解决措施

  • 第一种场景:缓存中大量数据同时失效,导致大量请求无法处理直接请求数据库

    img

    解决措施:

    (1)过期时间设置一个浮动值,当然这是得在业务场景运行的情况下

    (2)服务降级:服务降级是指针对不同类型的数据采取不同的处理方式,针对请求?。

    • 当业务应用访问的是非核心数据(例如电商商品属性)时,暂时停止从缓存中查询这些数据,而是直接返回预定义信息、空值或是错误信息;

    • 当业务应用访问的是核心数据(例如电商商品库存)时,仍然允许查询缓存,如果缓存缺失,也可以继续通过数据库读取。

      img

    • 第二种场景:redis实例发生宕机,无法处理请求,会导致大量请求打向数据库

      解决措施:

      (1)业务系统中实现服务熔断:就是发生缓存雪崩的时候,为了方式业务系统崩溃,业务系统调用缓存接口时,缓存客户端发现redis实例宕机了,则拒绝所有的请求并返回直至恢复。虽然这样可以保证数据库的正常运行,但是这样对业务影响比较大。

      (2)限流:针对熔断的改进就是限流了,允许少量请求进入,减轻数据库的压力

      (3)提前预防:构建redis的高可用集群,方式不可用的风险

3.缓存击穿

3.1.概念

热点数据失效后,针对该热点数据的大量请求打向数据库,与缓存雪崩的区别在于雪崩是大量请求失效,而击穿是热点数据缓存失效。

3.2.解决措施:

1.设置key永不过期

2.数据缺失从数据库同步入缓存的过程上分布式(非阻塞)锁,保证同步过程中只有一个请求去同步,其余请求直接拒绝

4.缓存穿透

4.1.概念

要访问的数据既不在缓存中也不在数据库中,此时大量请求会同时请求缓存及数据库,这个时候会同时给缓存和数据库带来压力

img

4.2.解决措施

(1)缓存空值或缺省值:针对数据缺失的数据存储空值或缺省值入redis

(2)前端预防,针对请求进行校验

(3)使用布隆过滤器快速判断数据是否存在,避免从数据库中查询数据是否存在,减轻数据库压力

布隆过滤器

布隆过滤器由一个初始值都为0的bit数组和N个哈希函数组成,用于快速判断数据是否存在。当我们想快速判断数据是否存在是,可以通过判断布隆过滤器的标记进行判断,标记流程如下:

  • 数据针对N个哈希函数进行计算得到N个哈希值
  • 再将N个哈希值对bit数据的长度取模。将取模后的值对应的下标置为1

如果数据存在,判断时再走一遍标记的流程,查看bit数组中这N个位置上的bit值。只要这N个bit值有一个不为1,这就表明布隆过滤器没有对该数据做过标记,则代表数据不存在,无需查找后端缓存和数据库

img

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×