redis面试:缓存雪崩、缓存击穿、缓存穿透

缓存系统的三大问题

缓存雪崩

问题

所谓雪崩就是原来有所支撑的冰雪,某一瞬间失去依托,瞬间涌下来。

  • 对比高并发系统,如果缓存系统故障,大量的请求无法从缓存完成数据请求,就全量冲向磁盘数据库系统,导致数据库被打死,整个系统彻底崩溃

原因一:缓存中的大量数据同时过期

描述

  • 由于缓存系统中的热点数据都有过期时间,如果没有过期时间就造成了主存和缓存的数据不一致,因此过期时间一般都不会太长
  • 设想某时刻一批热点数据同时在缓存系统中过期失效,那么这部分数据就都将请求磁盘数据库系统

redis面试:缓存雪崩、缓存击穿、缓存穿透_第1张图片

解决方案:在设置热点数据过期时间时尽量分散、加锁排队、数据预热、做二级缓存、服务降级

可以采用的方案大概有几种:

  • 在设置热点数据过期时间时尽量分散,比如设置100ms的基础值,在此基础上正负浮动10ms,从而降低相同时刻出现CacheMiss的key的数量。
  • 加锁排队:在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个 key 只允许一个线程查询数据和写缓存,其他线程等待;
    • 其中第一个线程发现CacheMiss之后进行加锁,再从数据库获取内容之后写到缓存中,其他线程获取锁失败则阻塞数ms之后再进行缓存读取,这样可以降低访问数据数据库的线程数,
    • 需要注意在单机和集群需要使用不同的锁,集群环境使用分布式锁来实现,但是由于锁的存在也会影响并发效率。
  • 数据预热:在业务层对使用的热点数据查看是否即将过期,如果即将过期则去数据库获取最新数据进行更新并延长该热点key在缓存系统中的时间,从而避免后面的过期CacheMiss,相当于把事情提前解决了。
  • 做二级缓存,或者双缓存策略:Cache1 为原始缓存,Cache2 为拷贝缓存,Cache1 失效时,可以访问 Cache2,Cache1 缓存失效时间设置为短期,Cache2 设置为长期。
  • 服务降级
    • 当业务应用访问的是非核心数据(例如电商商品属性)时,暂时停止从缓存中查询这些数据,而是直接返回预定义信息、空值或是错误信息;
    • 当业务应用访问的是核心数据(例如电商商品库存)时,仍然允许查询缓存,如果缓存缺失,也可以继续通过数据库读取。

缓存击穿的解决方法都有一定的权衡,实际中根据自己的需求来解决。

缓存击穿的影响一般来说并不会太大,或许在你的服务跑了很久之后你才意识到会有缓存击穿问题。

原因二:redis缓存实例发生故障宕机了

描述

造成缓存雪崩的主要原因是

  • Redis缓存实例发生故障宕机了,无法处理请求,这就会导致大量请求一下子积压到数据库层,从而发生缓存雪崩
  • 说白了就是缓存系统不够高可用,因此提高缓存系统的稳定性和可用性十分必要

解决方案:提高系统的可用性、请求限流机制

  • 事先预防—>提高系统的可用性:对于使用Redis作为缓存的系统而言可以使用哨兵机制、集群化、持久化等来提高缓存系统的HA
  • 事后应对—>在业务系统中实现服务熔断或者请求限流机制
    • 所谓服务熔断,是指在发生缓存雪崩时,为了防止引发连锁的数据库雪崩,甚至是整个系统的崩溃,我们暂停业务系统对缓存接口的访问
      • 在具体点说,就是业务应用调用缓存接口时,缓存客户端并不把请求发给redis缓存实例,而是直接返回,等到redis缓存实例恢复之后,在允许请求到缓存系统。这样,我们就避免了大量请求因为缓存缺失,而积压到数据库系统,保证了数据库系统的正常运行
      • 在业务系统运行时,我们可以监测 Redis 缓存所在机器和数据库所在机器的负载指标,例如每秒请求数、CPU 利用率、内存利用率等。如果我们发现 Redis 缓存实例宕机了,而数据库所在机器的负载压力突然增加(例如每秒请求数激增),此时,就发生缓存雪崩了大量请求被发送到数据库进行处理。我们可以启动服务熔断机制,暂停业务应用对缓存服务的访问,从而降低对数据库的访问压力。
    • 服务熔断虽然可以保证数据库的正常运行,但是暂停了整个缓存系统的访问,对业务应用的影响范围大。为了尽可能减少这种影响,我们也可以进行请求限流。这里说的请求限流,就是指,我们在业务系统的请求入口前端控制每秒进入系统的请求数避免过多的请求被发送到数据库。

缓存击穿

问题

  • 缓存击穿就是指,针对某个访问非常频繁的热点数据的请求,无法在缓存中进行处理,紧接着,访问该数据的大量请求,一下子都发送到了后端数据库,造成了数据库压力激增,会影响数据库处理其他请求
  • 缓存击穿的情况,经常发生在热点数据过期失效时

redis面试:缓存雪崩、缓存击穿、缓存穿透_第2张图片

解决方法

  • 对于访问频繁的热点数据,我们就不设置过期时间了。这样,对热点数据的访问,都可以在缓存中进行处理,而redis的高吞吐量可以很好地应对大量的并发请求访问。

redis面试:缓存雪崩、缓存击穿、缓存穿透_第3张图片

缓存穿透

问题

穿透形象一点就是:请求过来了,转了一圈,一无所获,就像穿过透明地带一样

缓存穿透指的是,在读数据的时候,没有命中缓存,请求“穿透”了缓存,直接访问后端数据库的情况。如果我们的缓存命中率比较低,就会出现大量“缓存穿透”的情况。

  • 少量的缓存穿透是正常的,我们需要预防的是,短时间内大量的请求无法命中缓存,请求穿透到数据库,导致数据库繁忙,请求超时。
  • 如果某时段有大量恶意的不存在的key的集中请求,那么服务器将一直处理这些根本不存在的请求,导致正常请求无法被处理,从而出现问题

redis面试:缓存雪崩、缓存击穿、缓存穿透_第4张图片
例子:拉面馆的服务员和厨师不允许拒绝已经进来的消费者,但是拉面馆的经营范围有限。此时恶意消费者点了一只5斤的澳洲龙虾,经过服务员和厨师都无法响应这个需求,此时轮流来了1000个这样的恶意消费者,拉面馆基本要歇菜了。

redis面试:缓存雪崩、缓存击穿、缓存穿透_第5张图片

解决方案

花钱

怎么解决:不让请求穿透缓存就可以了,反正现在存储也便宜,只要你买得起足够多的服务器,redis集群的容量是无限的,可以把全量的数据都放在redis集群里面,处理读请求的时候,干脆只读redis,不去读数据库。这样就完全没有“缓存穿透”的风险了,实际上很多大厂就是这么干的。

预热&&灰度发布

当系统初始化的时候,比如说系统升级重启或者缓存刚上线,这个时候缓存是空的,如果大量的请求直接打过来,很容易引发大量缓存穿透导致雪崩。

  • 为了避免这种情况,可以采用灰度发布的方式,先接入少量请求,再逐步增加系统的请求数量,直接全部请求都切换完成。

  • 如果系统不能采用灰度发布的方式,那就需要在系统启动的实时对缓存进行预热。所谓的缓存预热就是在系统初始化阶段,接收外部请求之前,先把最经常访问的数据填充到缓存里面,这样大量请求打过来的时候,就不会出现大量的缓存穿透了。

布隆过滤器

有效甄别是否存在这个key再决定是否读取很重要。常见的做法有:

  • 把不存在的key写一下NULL,这样就相当于命中了。但是这种方法局限性很大,缓存系统和数据库中存储大量无用key是无意义的,所以一般不建议
    redis面试:缓存雪崩、缓存击穿、缓存穿透_第6张图片

  • 另一种思路,转换为查找问题,类似于在海量数据中查找某个key是否存在,考虑空间复杂度和时间复杂度,一般选用布隆过滤器来实现。

    • 布隆过滤器是个好东西,有非常多的用途,包括:垃圾邮件识别、搜索蜘蛛爬虫url去重等,主要借助K个哈希函数和一个超大的bit数组来降低哈希冲突本身带来的误判,从而提高识别准确性。
    • 布隆过滤器由一个初值都为 0 的 bit 数组和 N 个哈希函数组成,可以用来快速判断某个数据是否存在。当我们想标记某个数据存在时(例如,数据已被写入数据库),布隆过滤器会通过三个操作完成标记:
      • 首先,使用 N 个哈希函数,分别计算这个数据的哈希值,得到 N 个哈希值。
      • 然后,我们把这 N 个哈希值对 bit 数组的长度取模,得到每个哈希值在数组中的对应位置。
      • 最后,我们把对应位置的 bit 位设置为 1,这就完成了在布隆过滤器中标记数据的操作。
    • 如果数据不存在(例如,数据库里没有写入数据),我们也就没有用布隆过滤器标记过数据,那么,bit 数组对应 bit 位的值仍然为 0。

布隆过滤器也存在一定的误判,假如判断存在可能不一定存在,但是假如判断不存在就一定不存在,因此刚好用在解决缓存穿透的key查找场景,事实上很多系统都是基于布隆过滤器来解决缓存穿透问题的。

redis面试:缓存雪崩、缓存击穿、缓存穿透_第7张图片

  • 最后一种方案是,在请求入口的前端进行请求检测。缓存穿透的一个原因是有大量的恶意请求访问不存在的数据,所以,一个有效的应对方案是在请求入口前端,对业务系统接收到的请求进行合法性检测,把恶意的请求(例如请求参数不合理、请求参数是非法值、请求字段不存在)直接过滤掉,不让它们访问后端缓存和数据库。这样一来,也就不会出现缓存穿透问题了

总结:

  • 设置空值:如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。
  • 布隆过滤器:将所有可能存在的数据哈希到一个足够大的 bitmap 中,一个一定不存在的数据会被这个 bitmap 拦截掉,从而避免了对底层存储系统的查询压力。
  • 在请求入口的前端进行请求检测:把恶意的请求(例如请求参数不合理、请求参数是非法值、请求字段不存在)直接过滤掉,不让它们访问后端缓存和数据库

小结

redis面试:缓存雪崩、缓存击穿、缓存穿透_第8张图片
另外,服务熔断、服务降级、请求限流这些方法都是属于“有损”方案,在保证数据库和整体系统稳定的同时,会对业务应用带来负面影响。例如使用服务降级时,有部分数据的请求就只能得到错误返回信息,无法正常处理。如果使用了服务熔断,那么,整个缓存系统的服务都被暂停了,影响的业务范围更大。而使用了请求限流机制后,整个业务系统的吞吐率会降低,能并发处理的用户请求会减少,会影响到用户体验。

建议,尽量采用预防式方案。比如

  • 针对缓存雪崩,可以合理的设置数据过期时间、搭建高可用的缓存集群等
  • 针对缓存击穿,在缓存访问非常频繁的热点数据时,不要设置过期时间等
  • 针对缓存穿透,提前在入口实现恶意请求检测、实现布隆过滤器等

你可能感兴趣的:(数据库,缓存,数据库,memcached)