缓存雪崩

概述

在高并发下,大量缓存key在同一时间失效,大量请求直接落在数据库上,导致数据库宕机。

如双十一期间,所有用户一打开淘宝就是进入首页,首页的压力非常大,为了提高并发,将网站首页数据都缓存到redis里,所有的redis key失效时间都是3小时。

双十一当天大量用户剁手狂欢,这时候3个小时过去了,redis里首页的key缓存全部失效,这时候redis里查询不到数据了,只能去数据库中查询,造成数据库无法响应挂掉。

image-20201027201417554

解决方案

  • 在批量往Redis存数据的时候,把每个Key的失效时间都加个随机值就好了,这样可以保证数据不会在同一时间大面积失效

    setRedis(Key, value, time + Math.random()*10000);
    
  • 若是集群部署,可将热点数据均匀分布在不同的Redis库中也能够避免key全部失效问题。⚠️:如果单个服务都是对应的单个Redis分片,是为了方便数据的管理,但是也同样有了可能会失效这样的弊端。
  • 不设置过期时间。有更新操作就更新缓存就好了(比如运维更新了首页商品,那你刷下缓存就完事了,不要设置过期时间),电商首页的数据也可以用这个操作,保险。
  • 跑定时任务,在缓存失效前刷进新的缓存

缓存穿透

概述

redis缓存和数据库中没有相关数据(例用户直接携带id<=0的参数不断发起请求),redis中没有这样的数据,无法进行拦截,直接被穿透到数据库,导致数据库压力过大宕机。

如果不对参数做校验,数据库id都是大于0的,我一直用小于0的参数去请求你,每次都能绕开Redis直接打到数据库,数据库也查不到,每次都这样,并发高点就容易崩掉了。

image-20201027201753514

解决方案

  • 对不存在的数据缓存到redis中,设置key,value值写为null、位置错误、稍后重试这样的值(具体取啥问产品),并设置一个短期过期时间段(如30s),避免过期时间过长影响正常用户使用。
  • 拉黑该IP地址。对单个IP每秒访问次数超出阈值的IP都拉黑。
  • 对参数进行校验,不合法参数进行拦截
  • ⭐️ 布隆过滤器(Bloom Filter) 将所有可能存在的数据哈希到一个足够大的bitmap(位图)中,一个一定不存在的数据会被 这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。

缓存击穿

概述

某一个热点key,在不停地扛着高并发,当这个热点key在失效的一瞬间,持续的高并发访问就击破缓存直接访问数据库,导致数据库宕机。

而缓存雪崩是因为大面积的缓存失效,打崩了DB,而缓存击穿不同的是缓存击穿是指一个Key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个Key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库。

image-20201027202611839

解决方案

  • 设置热点数据”永不过期”
  • 加上互斥锁/分布式锁:上面的现象是多个线程同时去查询数据库的这条数据,那么我们可以在第一个查询数据的请求上使用一个互斥锁来锁住它。这样,其他的线程走到这一步拿不到锁就等着,等第一个线程查询到了数据,然后将数据放到redis缓存起来。后面的线程进来发现已经有缓存了,就直接走缓存。
/**  
  * @description: 简单的分布式锁实现
  * @param: [key] 查询参数
  * @return: java.lang.String  数据
  * @author: Silince
  */
public static String getData(String key) throws InterruptedException {
  // 从 redis 查询数据
  String result = getDataByKV(key);
  // 参数校验
  if (StringUtils.isBlank(result)) { //代表缓存值过期
    // 获取锁
    if (reenLock.tryLock()) {
      // 去数据库查询
      result = getDataByDB(key);
      // 校验
      if (StringUtils.isNoneBlank(result)) {
        // 写入缓存
        setDataToKV(key, result);
      }
      // 释放锁 正常会在finally里面释放
      reenLock.unlock();
    } else {
      // 其他线程睡一会在去拿锁
      Thread.sleep(100L);
      result = getData(key);
    }
  }
  return result;
}

总结

雪崩是大面积的key缓存失效;穿透是redis里不存在这个缓存key;击穿是redis某一个热点key突然失效,最终的受害者都是数据库。

上线前准备:将redis、MySQL等搭建成高可用的集群,主从+哨兵,Redis cluster,避免全盘崩溃。

上线后处理方式:服务中进行限流 + 降级,防止MySQL被打崩溃。

宕机后处理方式:Redis 持久化 RDB+AOF,宕机重启,自动从磁盘上加载数据,快速恢复缓存数据。

限流 + 降级好处:

数据库绝对不会死,限流组件确保了每秒只有多少个请求能通过。只要数据库不死,就是说,对用户来说,3/5 的请求都是可以被处理的。只要有 3/5 的请求可以被处理,就意味着你的系统没死,对用户来说,可能就是点击几次刷不出来页面,但是多点几次,就可以刷出来一次。

这个在目前主流的互联网大厂里面是最常见的,你是不是好奇,某明星爆出什么事情,你发现你去微博怎么刷都空白界面,但是有的人又直接进了,你多刷几次也出来了,现在知道了吧,那是做了降级,牺牲部分用户的体验换来服务器的安全。