当前位置: 首页 > 后端技术 > Node.js

Redis缓存击穿、缓存穿透、缓存雪崩

时间:2023-04-03 23:40:47 Node.js

文章最初发表于公众号:程序员周先森。本平台不定期更新,喜欢我的文章,请关注我的微信公众号。上篇文章讲了Redis分布式锁,其实就是为了解释为什么用Redis做缓存而不用map/guava。缓存分为本地缓存和分布式缓存。以Java为例,使用内置的map/guava实现本地缓存。主要特点是轻量、快速,生命周期随着JVM的销毁而结束。而且,缓存在多实例状态下不是唯一的。使用Redis作为缓存称为分布式缓存。在多实例状态下,数据的缓存是共享的,缓存是一致的。因此,分布式条件下最适合的缓存方案是使用Redis来实现分布式缓存。本文主要讲Redis容易出现的三大问题:缓存击穿、缓存穿透、缓存雪崩。不过在介绍这三个问题之前,我们首先需要了解一下Redis中的key过期和淘汰机制。众所周知,Redis可以为存储在Redis中的缓存数据设置一个过期时间。比如我们获取的短信验证码,一般十分钟后就会过期。这时候我们需要在redis中存储验证码的时候加上一个key的过期时间,但是这里有一个需要特别注意的问题是:并不是key到了过期时间就会被redis删除。那么Redis是如何删除过期key的呢?Redis中删除过期键的策略有两种:常规删除和惰性删除。周期性删除:默认情况下,Redis每隔100ms随机选择一些设置了过期时间的Key,检查是否过期,如果过期则删除。为什么它是随机抽取而不是检查所有键?因为如果你设置了上万个key,每100毫秒检查一次所有存在的key,会对CPU造成很大的压力。惰性删除:定期删除可能会因为随机选择导致很多过期的Key在过期时间后被删除。所以当用户从缓存中获取数据时,redis会检查key是否过期,如果过期则删除key。这时候过期的key在查询的时候会从缓存中清除。但是,如果只使用定时删除+惰性删除的机制,还是会存在一个严重的隐患:如果定时删除很多过期的key,而用户已经很长时间没有使用过这些过期的key,过期的keys不能懒删除,导致过期keys在内存中堆积,最终导致Redis内存块耗尽。如何解决这个问题呢?这时候Redis的内存淘汰机制就应运而生了。Redis内存淘汰机制提供了六种数据淘汰策略:volatile-lru:从设置了过期时间的数据集中选择最近最少使用的数据进行淘汰。volatile-ttl:从设置了过期时间的数据集中选择并剔除即将过期的数据。volatile-random:从设置了过期时间的数据集中随机选择要淘汰的数据。allkeys-lru:当内存不足以容纳新写入的数据时,移除最近最少使用的key。allkeys-random:从数据集中随机选取数据进行淘汰。no-enviction:当内存不足以容纳新写入的数据时,新的写操作会报错。一般来说,推荐使用volatile-lru策略。对于配置信息等重要数据,不应该设置过期时间,这样Redis就永远不会淘汰这些重要数据。对于一般的数据,可以加一个缓存时间。当数据失效时,请求将从数据库中检索并重新存储在Redis中。缓存击穿说完Rediskey的过期和淘汰机制,我们可以进入正题:为什么会出现缓存击穿、缓存穿透、缓存雪崩?首先我们看请求是如何取数据的:当收到用户请求时,首先尝试从Redis缓存中取数据,如果能从缓存中取数据,则直接返回结果,当没有时缓存中的数据,会从DB中获取数据,如果数据库成功获取数据,更新Redis,然后返回数据,如果DB没有数据,返回空结果。那么在什么情况下会出现三大问题呢?先来看看缓存击穿的情况:定义:在高并发的情况下,某个流行的key突然过期,导致大量请求在Redis中找不到缓存的数据,然后全部访问DB请求数据,造成DB压力瞬间增加。解决方案:在缓存崩溃的情况下,一般不容易造成DB宕机,但会对DB造成周期性的压力。缓存击穿的解决方法一般可以是:Redis中的数据不设置过期时间,然后给缓存的对象添加一个属性来标识过期时间。每次获取数据时,检查对象中的过期时间属性。如果数据快过期时,异步发起一个线程主动更新缓存中的数据。但是,此解决方案可能会导致某些请求获取过期值。这取决于业务是否可以接受。如果需要的数据一定是新数据,最好的方案是将热点数据设置为永不过期,然后加一个mutex保证单线程写入缓存。缓存穿透的定义:缓存穿透是指查询缓存和DB中不存在的数据。比如通过id查询商品信息,id一般大于0,攻击者会故意将id传为-1进行查询。由于缓存不命中,会从DB中获取数据,这样就会导致每次缓存都没有命中数据,导致每次请求都访问DB,造成缓存穿透。解决方案:缓存穿透方案分为两部分:首先在API层增加基础检查:用户认证检查,id检查。比如用户认证失败或者直接拦截id<0的请求。其次,当cache和DB都拿不到数据时,key-value会以key-null的形式存入Redis。过期时间可存储最短60秒,防止攻击者在短时间内连续发起请求,造成数据库压力过大。有停机时间。缓存雪崩定义:如果缓存中的大量缓存在一段时间内过期,此时会发生大量缓存击穿,所有请求都会落到DB上。由于查询数据量巨大,会导致DB压力过大甚至导致DB宕机。解决方案:缓存雪崩一般没有完美的解决方案,但是我们可以尽可能分析用户行为,保证key过期时间比较平均,防止缓存的大量数据同时过期,并将热数据设置为永不过期。同时,如果是分布式环境,使用分布式锁保证缓存的单线程写入,可以防止大量缓存同时失效导致所有请求落在DB上。而我认为如果对于某些请求获取过期值是可以接受的话,最合理的方案其实是使用缓存击穿方案:Redis中的数据没有设置过期时间,然后在缓存的对象中添加一个属性来识别到期时间。第一次获取数据时,检查对象中的过期时间属性。如果数据即将过期,则异步发起一个线程主动更新缓存中的数据。欢迎关注公众号:程序员周先森。本文由博客多发平台OpenWrite发布!