redis缓存问题与双写一致性

缓存架构及优化

缓存架构中的问题

缓存雪崩

指的是缓存层支撑不住或者宕机掉后,流量会像疯狂打向后端存储层。由于缓存层承载着大量请求,有效的保护了存储层,但是如果缓存层由于某些原因不能提供服务,比如超大并发过来,缓存层支撑不住,或者由于缓存设计不好,类似大量请求访问bigkey,导致缓存能支撑的并发急剧下降,于是大量请求都会达到存储层,存储层的调用量会暴增,造成存储层也会级联宕机的情况。

解决方法:

  • 保证缓存层服务高可用性。
  • 使用熔断降级保护策略
  • 提前进行压测,提前暴露问题

缓存穿透

查询一个根本不存在的数据,缓存和数据库都不会命中,通常处于容错的考虑,如果存储层查不到数据则不写入缓存。缓存穿透将导致不存的数据每次请求都到存储层去查询,失去了缓存保护后端存储的意义。

第一,自身业务代码或者数据出现问题

第二,一些恶意攻击,爬虫等造成大量空命中。

解决方案:

  • 由于缓存中确实没有对应的key(每次都是不同的key时可采用布隆过滤器,布隆过滤器的缺点是不能删除或者修改某个数据,如果有改变需要重新进行初始化
  • 缓存空对象。如果每次都是同一个key,而且并发量很大时,可以使用分布式锁的方式,让一个请求去查数据库,其他的请求等待锁的释放,然后在查询缓存没查到则查数据库。当查到数据库之后及时更新缓存。减少后续请求对数据库的查询。

缓存击穿(失效)

由于大批量缓存在同一时间失效可能导致大量请求同时穿透缓存直达数据库,可能会造成数据库瞬间压力过大甚至挂掉,对于这种情况我们在批量增加缓存时最好将这一批数据的缓存过期时间加上一个随机数,不让他们在同一时间失效

由于缓存失效,导致请求都去查询数据库

  • 由于缓存时间一致,同一时间大部分key同时失效。
  • 热点key的问题,某个时刻某个没有被缓存的key突然被大量请求(分布式锁)

缓存双写一致性

当我们更新完数据库之后,为了保障缓存的有效性,我们应该及时更新缓存中的信息。 当多个线程同时对缓存和数据库进行更新操作时,就会出现缓存和数据库双写(即数据库和缓存的更新结果数据不一致)不一致的问题。

加缓冲的方式

  • 读数据库后更新缓存
  • 写数据库后更新缓存

具体可分为:并发读写导致的不一致,并发写导致的不一致

  • 如某一个线程查询完数据库结果在更新操作之前卡顿了一下(结果已经查出来放在内存中了),此时另一个线程对数据库做了更新操作,然后上述线程继续执行更新缓存的操作,就会出现缓存不一致的问题,前面执行的线程更新的数据和当前数据库存在的数据不一致。

redis缓存问题与双写一致性_第1张图片

  • 当两个线程同时对数据库和缓存进行写入操作的时候,如果第一个线程在更新完数据库,准备更新缓存的时候卡顿了,此时(第一个线程执行更新缓存之前),第二个线程也执行了一次更新数据库和缓存的操作。然后当第二个线程执行完毕之后,第一个线程继续了更新缓存的操作。最终结果是缓存的数据是来自第一个线程,数据库中的数据来自第二个线程,造成了缓存和数据的双写不一致问题。如图

redis缓存问题与双写一致性_第2张图片

上述情况是由于使用查出来的数据去更新缓存中的数据,此时查出来的数据可能已经不是最新的数据了导致的不一致问题,通常的解决方案为:修改数据库之后删除缓存。

  • 修改数据库之后删除缓存的情况,当一个查询的线程在更新缓存之前卡顿,会导致下图情况,实际最终更新缓存的数据为线程1写入数据库的数据,而此时数据库最新的数据是线程2写入的数据。此时又会造成数据库和缓存不一致的问题,与第一点相似

redis缓存问题与双写一致性_第3张图片

解决方案:

  1. 对于并发几率很小的数据(如个人维度的订单数据,用户数据)。这种就不用考虑这个问题,很少会发生缓存不一致。可以给缓存数据加上一个过期时间,每个一段时间触发一次读的主动更新操作
  2. 就算并发很高,如果业务上能容忍短时间的缓存数据不一致问题,如商品名称、商品分类菜单等,缓存加上过期时间依然能够解决大部分业务对于缓存的要求
  3. 如果不能容忍缓存数据不一致,可以通过加上读写锁保证并发读写或者写写的时候按着顺序排好队。读读的时候相当于无锁
  4. 也可以用阿里开源的canal通过监听数据库的binlog日志及时去修改缓存,但是引入中间件会增加系统的复杂度

延时删除

为了避免上述并发情况导致的数据库和缓存不一致的问题,可以采用延时删除的策略,但是这种策略也避免不了某些特殊的情况。即如果延时删除的时间不够,那么也会出现和上图一致的情况。

延时双删

为了防止小概率被其他读线程在当前写线程删除缓存之后,缓存旧的数据,所以就出现了一种在删除缓存数据之后睡眠一段时间在进行删除,保证其他读线程更新的旧的缓存数据也失效。首先这种方案也完全解决不了上述不一致的问题。况且这种方案睡眠会带来一定的性能问题。所以还不如使用分布式读写锁的方式去解决。

分布式锁

在无法容忍的情况下可采用分布式锁的方式解决上述并发读写和并发写导致的数据库和缓存一致性的问题,如果是那种读多写少的场景,可以采用分布式读写锁的方式解决适当提高锁的效率。

最终有效的解决方案:具体情况具体分析。如果可以容忍上述极少情况不一致的问题,可以不解决上述问题。如果不能容忍,那么可以采用分布式锁来解决,避免多个线程同时执行。

你可能感兴趣的:(redis,缓存,redis,java)