目录
缓存雪崩
缓存雪崩是指大量的应用请求无法在 Redis 缓存中进行处理,紧接着应用将大量请求发 送到数据库层,导致数据库层的压力激增。
缓存雪崩的一个原因是:缓存中有大量数据同时过期,导致大量请求无法得到处理。解决办法:避免给大量的数据设置相同的过期时间。
现在有个电商平台双十一抢购,00:00 的时候把要抢购的一波商品同时放入了缓存,缓存过期的时间比较集中,假设缓存一个小时,那么到了凌晨一点钟的时候这批商品的缓存就都过期了。而对这批商品的访问查询,都落到了数据库上,对于数据库而言,就会产生周期性的压力波峰。缓存雪崩最严重的问题是,在流量高峰的时候,某个缓存服务器的节点出现宕机或出现问题,导致缓存流量透传到数据库上面,更严重的是某个缓存服务器出问题,导致缓存集群出现问题。
解决方案:将缓存失效时间分散开,比如可以在原有的失效时间基础上去增加一个随机值,比如1~5分钟随机,这样每一个缓存的过期时间的重复率就会降低,也就很难引发集体失效的事件了。
如果已经发生了缓存雪崩,可以通过服务降级来应对缓存雪崩。针对不同的数据采取不同的处理方式:
- 当业务应用访问的是非核心数据(例如电商商品属性)时,暂时停止从缓存中查询这些数据,而是直接返回预定义信息、空值或是错误信息;
- 当业务应用访问的是核心数据(例如电商商品库存)时,仍然允许查询缓存,如果缓存缺失,也可以继续通过数据库读取。
缓存雪崩的第二个原因是: Redis 缓存实例发生故障宕机了,无法处理请求,这就会导致大量请求一下子积压到数据库层, 从而发生缓存雪崩。
一个 Redis 实例可以支持数万级别的请求处理吞吐量,而单个数据库可能只能 支持数千级别的请求处理吞吐量,它们两个的处理能力可能相差了近十倍。由于缓存雪 崩,Redis 缓存失效,所以,数据库就可能要承受近十倍的请求压力,从而因为压力过大 而崩溃。
- 解决方案1:是在业务系统中实现服务熔断或请求限流机制。业务应用调用 缓存接口时,缓存客户端并不把请求发给 Redis 缓存实例,而是直接返回,等到 Redis 缓 存实例重新恢复服务后,再允许应用请求发送到缓存系统。
- 解决方案2:为了尽可能减少影响,可以进行请求限流。我们在业务系统的请求入口前端控制每秒进入系统的请求数,避免过多的请求被发送到数据库。
- 解决方案3:提前预防,通过主从节点的方式构建 Redis 缓存高可靠集群。如果 Redis 缓存的主节点故障宕机了, 从节点还可以切换成为主节点,继续提供缓存服务,避免了由于缓存实例宕机而导致的缓 存雪崩问题。
缓存击穿
缓存击穿是指针对某个访问非常频繁的热点数据的请求无法在缓存中进行处理,紧接着访问该数据的大量请求一下子都发送到了后端数据库,导致了数据库压力激增,会影响数据库处理其他请求。假设有个场景:一个key非常热点(比如微博上的某个热点话题),在不停的扛着大并发,当这个key在失效的瞬间,持续的大并发就穿破缓存直接请求数据库,就像在一个屏障上凿开了一个洞。像这种情况一般的解决方案:对于访问特别频繁的热点数据就不设置过期时间了,让key永久有效,然后手动控制删除key。
缓存穿透
缓存穿透是指要访问的数据既不在 Redis 缓存中也不在数据库中,同时给缓存和数据库带来巨大压力。当系统初始化的时候,比如说系统升级重启或者是缓存刚上线,这个时候缓存是空的,如果大量的请求直接打过来,很容易引发大量缓存穿透导致雪崩。为了避免这种情况,可以采用灰度发布的方式,先接入少量请求,再逐步增加系统的请求数量,直到全部请求都切换完成。如果系统不能采用灰度发布的方式,那就需要在系统启动的时候对缓存进行预热。所谓的缓存预热就是在系统初始化阶段,接收外部请求之前,先把最经常访问的数据填充到缓存里面,这样大量请求打过来的时候,就不会出现大量的缓存穿透了。
假设有 1亿 条用户数据,Redis 中没有的用户再去数据库中查询,现在可能会存在一种恶意请求,这个请求携带上了很多不存在的用户,这个时候 Redis 无法拦截下来请求,所以请求会直接跑到数据库里去。这个时候,这些恶意请求会击穿缓存甚至数据库,进而引起“雪崩效应”。
为了解决这个问题,可以使用布隆过滤器,将 1亿条用户数据存在 Redis 中不显示,但是可以存在布隆过滤器中。请求来了首先去布隆过滤器中判断数据是否存在,如果存在再去数据库中查询,否则就不去数据库中查询。注意:布隆过滤器中没有的,那就一定没有。布隆过滤器中有的,也不一定就真的有。
解决方案:
- 第一种方案是,缓存空值或缺省值。记得给这个空值加一个比较短的过期时间,让空值在短时间之内能够快速过期淘汰。
- 第二种方案是,使用布隆过滤器快速判断数据是否存在,避免从数据库中查询数据是否存在,减轻数据库压力。
- 第三种方案是,在请求入口的前端进行请求检测。
- 再补充一种:自定义id加密规则校验拦截非法请求。
使用布隆过滤器解决缓存穿透
布隆过滤器:把集合中的每一个值按照提供的 Hash 算法算出对应的 Hash 值,然后将 Hash 值对数组长度取模后得到需要计入数组的索引值,并且将数组这个位置的值从 0 改成 1。在判断一个元素是否存在于这个集合中时,你只需要将这个元素按照相同的算法计算出索引值,如果这个位置的值为 1 就认为这个元素在集合中,否则则认为不在集合中。
初始化一个很大的数组,比方说长度为 20 亿的数组,然后选择一个 Hash 算法,将目前现有的所有用户的 ID 计算出 Hash 值并且映射到这个大数组中,映射位置的值设置为 1,其它值设置为 0。新注册的用户除了需要写入到数据库中之外,它也需要依照同样的算法更新布隆过滤器的数组中相应位置的值。那么当需要查询某一个用户的信息时,首先查询这个 ID 在布隆过滤器中是否存在,如果不存在就直接返回空值,而不需要继续查询数据库和缓存,这样就可以极大地减少异常查询带来的缓存穿透。
布隆过滤器的缺陷:
1. 在判断元素是否在集合中时是有一定错误几率的,比如它会把不是集合中的元素判断为处在集合中;
2. 不支持删除元素(可以增加个计数器来实现,所有节点的计数器减为0则删除)。
总结:要依据业务场景来选择是否能够使用布隆过滤器,比如像是注册用户的场景下,因为用户删除的情况基本不存在,所以还是可以使用布隆过滤器来解决缓存穿透的问题的。
参考资料:15 | 缓存的使用姿势(三):缓存穿透了怎么办?-极客时间
总结:缓存雪崩、缓存击穿、穿透穿透的解决方案总结:
- 针对缓存雪崩,合理地设置数据过期时间,以及搭建高可靠缓存集群;
- 针对缓存击穿,在缓存访问非常频繁的热点数据时,不要设置过期时间;
- 针对缓存穿透,提前在入口前端实现恶意请求检测,或者规范数据库的数据删除操作避免误删除,使用布隆过滤器。