11-1 缓存的收益与成本

收益

  • 加速读写
  • 降低后端负载

    成本

  • 数据不一致:缓存层和数据层有时间窗口不一致,和更新策略有关。

  • 代码维护成本:多了一层缓存逻辑。
  • 运维成本:Redis的集群、可靠性。

    使用场景

  1. 降低后端负载:
    • 对高消耗的SQL结果进行缓存。
  2. 加速请求响应。
  3. 将大量写合并为批量写。
    • 如计数器先Redis累加在批量写DB。

11-2 缓存的更新策略

  • 达到最大内存时删除(maxmemory-policy)。
  • 过期时删除。
  • 由应用层自己控制。

11-3 缓存粒度控制


11-4 缓存穿透问题

什么是缓存穿透?

缓存穿透的概念很简单,用户想要查询一个数据,发现redis内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中,于是都去请求了持久层数据库。这会给持久层数据库造成很大的压力。这就是缓存穿透。
image.png
这里需要注意和缓存击穿的区别,缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。

产生原因

  1. 业务代码自身有问题,试图访问不存在的数据。
  2. 恶意攻击、爬虫等。

    如何发现

  3. 业务的响应时间。

  4. 相关监控指标:总调用数、缓存层命中数、存储层命中数。

    解决方案

    方案1:缓存空对象

    image.png
    可能存在的问题:
  • 需要更多的键。在面对恶意攻击时,查询参数可能是随机生成的无意义的数值。

    方案2:布隆过滤器拦截

    布隆过滤器可用于判断一个元素是否在一个超大的集合中存在。对于缓存穿透的场景,先将所有合法的键放入布隆过滤器,然后当有请求到达时,先用布隆过滤器判断要请求的键是否真实存在,如果存在,才进行后续查询。如果不存在,则直接返回miss。

    布隆过滤器可视为一个大型HashSet。Redis本身没有提供布隆过滤器,但是可以在应用层实现布隆过滤逻辑,然后底层存储用Redis的Bitmap(String)。

image.png
对于布隆过滤器,一个比较麻烦的问题是怎么生成这样一个布隆过滤器。如果针对的是固定数据,那么比较简单,只需要初始化时生成一次。但是对于频繁更新的数据,那么实时更新布隆过滤器就比较麻烦。


11-5 缓存雪崩问题

什么是缓存雪崩?

在高并发下,大量的缓存key在同一时间失效(可能是由于key过期,也可能是由于Redis宕机),导致大量的请求落到数据库上,进而造成级联故障。如活动系统里面同时进行着非常多的活动,但是在某个时间点所有的活动缓存全部过期。

注意对比缓存雪崩缓存击穿。缓存雪崩是指几乎所有缓存都失效,而缓存击穿是指热点数据单条失效。

优化方案

  1. 保证Redis服务本身高可用,避免发生单点故障。
  2. 限流降级。这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。(以部分请求的响应延长或失败,来换取整个数据层的安全)。
  3. 数据预热。数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。
  4. 缓存过期时间设置为滑动的。
  5. 提前演练:压力测试。

    了解Hystrix


11-6 无底洞问题

什么是无底洞问题?


11-7 热点key的重建问题(缓存击穿问题)

问题描述

当某个热点key还不存在缓存时,同一时间突然到达很多请求。每一个请求都会发现缓存不存在,然后访问底层数据库,然后重建缓存。这就会造成底层数据库压力,并且响应时间也很慢。
image.png

解决方案

(1)互斥锁

对查询数据库、冲减缓冲,这个步骤加互斥锁(写锁,排它锁),即同一时间只允许一个线程执行重建操作。这里的互斥锁可以用redis来实现。
image.png

(2)永远不过期

这实际上是保证缓存不被击穿的一种方法。
当第一次设置key的缓存让,让该缓存永不过期,这样就不会出现“缓存击穿”的问题。然后,在应用层判断该缓存是否过期。如果是,就让一个专门的线程去异步更新这个缓存。
这种方案存在的问题是,在数据过期之后,缓存依然会在一段时间内有效,从而造成一定的同步性问题。