缓存穿透

缓存穿透:(1)key 对应的数据是不存在的,即缓存中没有数据,所有的请求都会去访问后台数据库。黑客试图用该方法攻击数据库。(2)缓存数据生成耗费大量时间或者资源,业务卡住导致缓存没生成。

针对 key 不存在的问题:

  • 增加校验拦截不正常数据,如 id<0;
  • 使用 null 缓存无效 key,Key = 表名:列名:主键名:主键值 。
  • 使用 bitmaps 类型定义一个可供访问的白名单,对不能访问数据进行拦截;
  • 采用布隆过滤器过滤大部分错误数据 (+二级缓存);
  • 根据用户或者 IP 对接口进行限流,对于异常频繁的访问行为,还可以采取黑名单机制,例如将异常 IP 列入黑名单。

针对缓存数据生成时间长的问题,典型的应用场景就是电商分页访问某个分类的时候,商品数据量多,若将搜有数据缓存起来不现实,将所有分页缓存也不现实。

  1. 分页缓存的有效期设置为 1 天,因为设置太长时间的话,缓存不能反应真实的数据。
  2. 通常情况下,用户不会从第 1 页到最后 1 页全部看完,一般用户访问集中在前 10 页,因此第 10 页以后的缓存过期失效的可能性很大。
  3. 竞争对手每周来爬取数据,爬虫会将所有分类的所有数据全部遍历,从第 1 页到最后 1 页全部都会读取,此时很多分页缓存可能都失效了。
  4. 由于很多分页都没有缓存数据,从数据库中生成缓存数据又非常耗费性能(order by limit 操作),因此爬虫会将整个数据库全部拖慢。

缓存击穿

缓存击穿:该 key 对应的数据是存在的,但是访问缓存时数据过期或没有数据,所有的请求都会访问后台数据库。某个热点数据某时刻刚好过期。

缓存击穿解决:

  • 永不过期:设置热点数据永不过期或者过期时间比较长。
  • 提前预热:针对热点数据提前预热,将其存入缓存中并设置合理的过期时间比如秒杀场景下的数据在秒杀结束之前不过期。
  • 加锁:在缓存失效后,使用 mutex 互斥锁,先使用 setnx,成功后再访问数据库并回设缓存,否则重试。
  • 动态调整:动态调整热门数据过期时间,快过期了更新过期时间

缓存雪崩

缓存雪崩:多个 key 对应的数据时存在的,但是访问缓存中数据过期或没有数据,所有请求都会访问后台数据库。缓存服务器重启或大量缓存在某一个时间段一起失效。

缓存雪崩的情况:

  • Redis 服务不可用
  • 大量缓存同时失效

针对 Redis 服务不可用的情况:

  1. Redis 集群:采用 Redis 集群,避免单机出现问题整个缓存服务都没办法使用。Redis Cluster 和 Redis Sentinel 是两种最常用的 Redis 集群实现方案,详细介绍可以参考:Redis 集群详解(付费)
  2. 多级缓存:设置多级缓存,例如本地缓存+Redis 缓存的二级缓存组合,当 Redis 缓存出现问题时,还可以从本地缓存中获取到部分数据。
  3. 熔断机制、限流机制。当流量达到一定值,直接返回 502.

针对大量缓存同时失效的情况:

  1. 设置随机失效时间:为缓存设置随机的失效时间,例如在固定过期时间的基础上加上一个随机值,这样可以避免大量缓存同时到期,从而减少缓存雪崩的风险。
  2. 提前预热:针对热点数据提前预热,将其存入缓存中并设置合理的过期时间比如秒杀场景下的数据在秒杀结束之前不过期。
  3. 持久缓存策略:虽然一般不推荐设置缓存永不过期,但对于某些关键性和变化不频繁的数据,可以考虑这种策略。
  4. 设置过期标志,待过期后通知其他线程更新缓存
  5. 熔断机制、限流机制。当流量达到一定值,直接返回 502.

对于缓存雪崩问题,我们采取了双 key 策略:要缓存的 key 过期时间是 t,key1 没有过期时间。每次缓存读取不到 key 时就返回 key1 的内容,然后触发一个事件。这个事件会同时更新 key 和 key1。(不过成本太高了。相当于成本翻倍。)

缓存预热如何实现

常见的缓存预热方式有两种:

  • 使用定时任务,比如 xxl-job,来定时触发缓存预热的逻辑,将数据库中的热点数据查询出来并存入缓存中。
  • 使用消息队列,比如 Kafka,来异步地进行缓存预热,将数据库中的热点数据的主键或者 ID 发送到消息队列中,然后由缓存服务消费消息队列中的数据,根据主键或者 ID 查询数据库并更新缓存。