「Redis」Redis穿透、击穿、雪崩和数据一致性

1. 缓存穿透

访问一个不存在的key,缓存不起作用,请求会穿透到DB,流量大时DB会挂掉。

  • 解决方案:
    1. 采用布隆过滤器(bloomfilter就类似于一个hash set),使用一个足够大的bitmap,用于存储可能访问的key,不存在的key直接被过滤;
    2. 访问key未在DB查询到值,也将空值写进缓存,但可以设置较短过期时间。
    3. 接口限流与熔断、降级
    4. 使用互斥锁排队(分布式环境中要使用分布式锁,单机的话用普通的锁(synchronized、Lock))

2. 缓存雪崩

大量的key设置了相同的过期时间,导致在缓存在同一时刻全部失效,造成瞬时DB请求量大、压力骤增,引起雪崩。

  • 解决方案
    1. 可以给缓存设置过期时间时加上一个随机值时间,使得每个key的过期时间分布开来,不会集中在同一时刻失效。
    2. 建立备份缓存,缓存A和缓存B,A设置超时时间,B不设值超时时间,先从A读缓存,A没有读B,并且更新A缓存和B缓存;
    3. 加锁排队,实现同上;

3. 缓存击穿

一个存在的key,在缓存过期的一刻,同时有大量的请求,这些请求都会击穿到DB,造成瞬时DB请求量大、压力骤增。

  • 解决方案
    • 在访问key之前,采用SETNX(set if not exists)来设置另一个短期key来锁住当前key的访问,访问结束再删除该短期key。

4. 缓存并发竞争

多个redis的client同时set key引起的并发问题(例如:多客户端同时并发写一个key,一个key的值是1,本来按顺序修改为2,3,4,最后是4,但是顺序变成了4,3,2,最后变成了2)

  • 解决方案
    1. 如果对这个key操作,不要求顺序:准备一个分布式锁,大家去抢锁,抢到锁就做set操作即可。
    2. 如果对这个key操作,要求顺序:
      1. 分布式锁+时间戳(假设系统B先抢到锁,将key1设置为{ValueB 7:05}。接下来系统A抢到锁,发现自己的key1的时间戳早于缓存中的时间戳(7:00<7:05),那就不做set操作了)
      2. 利用消息队列(把Redis.set操作放在队列中使其串行化,必须的一个一个执行)

5. 缓存和数据库一致性解决方案

5.1 并发量、一致性要求都不是很高的场景

  • 写流程:先淘汰缓存,再写数据库,之后再异步将数据刷回缓存
  • 读流程:先读缓存,如果缓存没读到,则去读DB,之后再异步将数据刷回缓存
  1. 优点:实现起来简单,异步刷新,补缺补漏
  2. 缺点:容灾不足,并发问题,一个比较大的缺陷在于刷新缓存有可能会失败,而失败之后缓存中数据就一直会处于错误状态,所以它并不能保证数据的最终一致性

5.2 业务简单,读写QPS比较低的场景(QPS每秒查询率(Query Per Second))

  • 写流程:先淘汰缓存,再写数据库,监听从库binlog,通过解析binlog来刷新缓存
  • 读流程:第一步先读缓存,如果缓存没读到,则去读DB,之后再异步将数据刷回缓存
  1. 优点:容灾
  2. 缺点:只适合简单业务,复杂业务容易发生并发问题(例如:读/写的时候,缓存中的数据已失效,此时又发生了更新)

5.3 业务只需要达到“最终一致性”要求的场景

  • 写流程:先淘汰缓存,再写数据库,监听从库binlog,通过分析binlog我们解析出需要需要刷新的数据标识,然后将数据标识写入MQ,接下来就消费MQ,解析MQ消息来读库获取相应的数据刷新缓存。
  • 读流程:第一步先读缓存,如果缓存没读到,则去读DB,之后再异步将数据标识写入MQ(这里MQ与写流程的MQ是同一个),接下来就消费MQ,解析MQ消息来读库获取相应的数据刷新缓存。
  1. 优点:容灾完善,无并发问题
  2. 缺点:只能达到”最终一致性”

5.4 强一致性的场景

  • 写流程:我们把修改的数据通过Cache_0标记“正在被修改”,如果标记成功,写数据库,删除缓存,监听从库binlog,通过分析binlog我们解析出需要需要刷新的数据标识,然后将数据标识写入MQ,接下来就消费MQ,解析MQ消息来读库获取相应的数据刷新缓存; 那如果标记失败,则要放弃这次修改。
  • 读流程:先读Cache_0,看看要读的数据是否被标记,如果被标记,则直接读主库;如果没有被标记,读缓存,如果缓存没读到,则去读DB,之后再异步将数据标识写入MQ(这里MQ与写流程的MQ是同一个),接下来就消费MQ,解析MQ消息来读库获取相应的数据刷新缓存。
  1. 优点:容灾完善,无并发问题
  2. 缺点:增加Cache_0强依赖,复杂度是比较高的(涉及到Databus、MQ、定时任务等等组件)