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

缓存穿透

什么是缓存穿透

缓存穿透说简单点就是大量请求的 key 根本不存在于缓存中,导致请求直接到了数据库上,根本没有经过缓存这一层。举个例子:某个黑客故意制造我们缓存中不存在的 key 发起大量请求,导致大量请求落到数据库。

缓存穿透的大致流程

如下图所示,用户的请求最终都要跑到数据库中查询一遍。

 解决办法

最基本的就是首先做好参数校验,一些不合法的参数请求直接抛出异常信息返回给客户端。比如查询的数据库 id 不能小于 0、传入的邮箱格式不对的时候直接返回错误消息给客户端等等。

1)缓存无效 key

如果缓存和数据库都查不到某个 key 的数据就写一个到 Redis 中去并设置过期时间,具体命令如下: SET key value EX 10086 。这种方式可以解决请求的 key 变化不频繁的情况,如果黑客恶意攻击,每次构建不同的请求 key,会导致 Redis 中缓存大量无效的 key 。很明显,这种方案并不能从根本上解决此问题。如果非要用这种方式来解决穿透问题的话,尽量将无效的 key 的过期时间设置短一点比如 1 分钟。

另外,这里多说一嘴,一般情况下我们是这样设计 key 的: 表名:列名:主键名:主键值 。

2)布隆过滤器

布隆过滤器是一个非常神奇的数据结构,通过它我们可以非常方便地判断一个给定数据是否存在于海量数据中。我们需要的就是判断 key 是否合法,有没有感觉布隆过滤器就是我们想要找的那个“人”。

具体是这样做的:把所有可能存在的请求的值都存放在布隆过滤器中,当用户请求过来,先判断用户发来的请求的值是否存在于布隆过滤器中。不存在的话,直接返回请求参数错误信息给客户端,存在的话才会走下面的流程。

加入布隆过滤器之后的缓存处理流程图如下。

但是,需要注意的是布隆过滤器可能会存在误判的情况。总结来说就是: 布隆过滤器说某个元素存在,小概率会误判。布隆过滤器说某个元素不在,那么这个元素一定不在。

为什么会出现误判的情况呢? 我们还要从布隆过滤器的原理来说!

我们先来看一下,当一个元素加入布隆过滤器中的时候,会进行哪些操作:

  1. 使用布隆过滤器中的哈希函数对元素值进行计算,得到哈希值(有几个哈希函数得到几个哈希值)。
  2. 根据得到的哈希值,在位数组中把对应下标的值置为 1。

我们再来看一下,当我们需要判断一个元素是否存在于布隆过滤器的时候,会进行哪些操作:

  1. 对给定元素再次进行相同的哈希计算;
  2. 得到值之后判断位数组中的每个元素是否都为 1,如果值都为 1,那么说明这个值在布隆过滤器中,如果存在一个值不为 1,说明该元素不在布隆过滤器中。

然后,一定会出现这样一种情况:不同的字符串可能哈希出来的位置相同。 (可以适当增加位数组大小或者调整我们的哈希函数来降低概率)

缓存击穿

什么是缓存击穿

缓存击穿是指在缓存系统中,某个被大量访问的热点数据在缓存中的有效期刚好到期,而此时大量并发请求同时过来访问该数据,由于缓存中已无此数据,这些请求就会同时绕过缓存直接去访问数据库,从而导致数据库瞬间承受巨大的查询压力,可能会影响数据库甚至整个系统的正常运行。

解决方案

常见的解决方案有两种:

互斥锁

互斥锁的解决思路就是在Redis进行缓存重建时,拿到一个互斥锁,其他请求拿不到这个锁就是乖乖等待锁的释放。

逻辑过期

逻辑过期的解决思路如下:

在存入redis的value中增加一个字段,该字段为过期时间加上x分钟,通过计算就知道这个数据是否逻辑上过期,事实上没过期一直存在redis中。

在redis进行缓存重建的时候,会另开一个线程进行重建并拿到互斥锁,其他线程拿不到数据想要缓存重建时也拿不到锁,那就直接返回旧数据。

互斥锁方案:由于保证了互斥性,所以数据一致,且实现简单,因为仅仅只需要加一把锁而已,也没其他的事情需要操心,所以没有额外的内存消耗,缺点在于有锁就有死锁问题的发生,且只能串行执行性能肯定受到影响

逻辑过期方案: 线程读取过程中不需要等待,性能好,有一个额外的线程持有锁去进行重构数据,但是在重构数据完成前,其他的线程只能返回之前的数据,且实现起来麻烦

缓存雪崩

什么是缓存雪崩?

实际上,缓存雪崩描述的就是这样一个简单的场景:缓存在同一时间大面积的失效,后面的请求都直接落到了数据库上,造成数据库短时间内承受大量请求。 这就好比雪崩一样,摧枯拉朽之势,数据库的压力可想而知,可能直接就被这么多请求弄宕机了。

举个例子:系统的缓存模块出了问题比如宕机导致不可用。造成系统的所有访问,都要走数据库。

还有一种缓存雪崩的场景是:有一些被大量访问数据(热点缓存)在某一时刻大面积失效,导致对应的请求直接落到了数据库上。 这样的情况,有下面几种解决办法:

举个例子 :秒杀开始 12 个小时之前,我们统一存放了一批商品到 Redis 中,设置的缓存过期时间也是 12 个小时,那么秒杀开始的时候,这些秒杀的商品的访问直接就失效了。导致的情况就是,相应的请求直接就落到了数据库上,就像雪崩一样可怕。

解决办法

(1)给不同的Key的TTL添加随机值(推荐)
        操作简单,当我们在做缓存预热的时候,就有可能在同一时间批量插入大量的数据,
那么如果它们的TTL都一样的话就可能出现大量key同时过期的情况!!!
所以我们需要在设置过期时间TTL的时候,定义一个范围,追加该范围内的一个随机数。
(2)利用Redis集群提高服务的可用性
        使用集群提高可靠性
(3)给缓存业务添加降级限流策略
        微服务的知识
(4)给业务添加多级缓存  
        请求到达浏览器,nginx可以做缓存,未命中找Redis,再未命中找JVM,最后到数据库......

 总结

我个人感觉redis的缓存穿透和缓存击穿有部分是类似,而缓存击穿和缓存雪崩又有一部分是类似的

缓存穿透和缓存击穿的大至区别

  • 缓存击穿指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。
  • 缓存穿透指查询一个根本不存在的数据,缓存层和存储层都不会命中,每次都会去请求数据库,若有大量这样的请求,可能会导致数据库压力过大甚至崩溃。

缓存击穿和缓存雪崩的大至区别

  • 涉及数据范围缓存击穿主要针对的是单个热点数据,是由于单个关键数据的缓存过期,大量请求同时访问这一数据而引发问题;而缓存雪崩涉及的是大量数据,是大量缓存数据在同一时间或短时间内集体失效,导致大量请求冲向数据库。
  • 引发原因侧重:缓存击穿更多是因为某个热点数据的缓存时间设置不当,或者在缓存过期的瞬间有大量并发请求访问;缓存雪崩除了可能因为缓存时间设置不合理外,还可能由于缓存服务器故障、大量数据同时更新等原因导致大量缓存数据同时失效
  • 问题表现形式缓存击穿是大量请求集中访问某一个特定数据缓存雪崩是大量请求分散地访问多个不同的数据,但这些数据的缓存同时失效,导致整体请求流量对数据库造成巨大冲击。

文章参考

Redis的缓存穿透、缓存雪崩、缓存击穿问题及有效解决方案_缓存雪崩和缓存穿透问题解决方案-CSDN博客

### Redis缓存击穿穿透雪崩及限流的概念 #### 缓存击穿 (Cache Breakdown) 当某个热点键(即访问频率极高的键)突然失效时,如果此时大量并发请求同时到达并尝试获取该键对应的值,则会直接打到数据库上,造成数据库压力骤增甚至崩溃的现象称为缓存击穿。 为了防止这种情况发生,通常采用以下几种方法来应对: - **加锁机制**:对于高并发场景下的热点key操作,可以在读取缓存前加上分布式锁。只有获得锁的服务实例才能去查询DB并将结果写回缓存[^3]。 - **设置合理的过期时间**:避免所有缓存项在同一时刻集体到期,可以通过随机化每个缓存条目的TTL(Time To Live),使得它们不会集中在一个时间段内全部过期[^4]。 - **逻辑过期方案**:除了物理删除外还可以给缓存记录附加一个额外的时间戳字段表示其实际有效期;即使超过了这个期限也不会立即清除而是标记为已过期状态等待下一次更新后再真正移除。 ```python import time from redis import StrictRedis def get_with_logical_expiry(redis_client, key): value = redis_client.get(key) if not value: return None data, expiry_time_str = value.split('|') current_time = int(time.time()) expiry_time = int(expiry_time_str) if current_time >= expiry_time: # 已经过期,返回None让调用者重新加载数据 return None return data ``` --- #### 缓存穿透 (Cache Penetration) 指的是恶意攻击者故意构造不存在的数据ID进行查询,由于这些ID对应的数据根本就不存在于数据库中,所以每次都会导致穿透整个缓存层直抵底层存储系统,从而浪费资源并可能引发性能瓶颈。 针对这一现象的有效措施有: - **缓存空对象**:对于确实不存在的数据,在第一次查库确认之后将其置入缓存一段时间,这样后续相同的请求就可以直接命中缓存而无需再次访问数据库了。 - **布隆过滤器(Bloom Filter)**:这是一种空间效率非常高的概率型数据结构,能够快速判断某元素是否属于集合之一员。虽然存在一定误判率但是非常适合用来做前置拦截以减少不必要的缓存查找次数[^1]。 ```python class BloomFilter(object): def __init__(self, size=500000, hash_num=7): self.bit_array = bitarray(size) self.bit_array.setall(0) self.hash_functions = [] for i in range(hash_num): seed = random.randint(0, MAX_SEED) self.hash_functions.append(mmh3.Hash(seed)) def add(self, item): for f in self.hash_functions: pos = f(item) % len(self.bit_array) self.bit_array[pos] = True def test(self, item): for f in self.hash_functions: pos = f(item) % len(self.bit_array) if not self.bit_array[pos]: return False return True ``` --- #### 缓存雪崩 (Cache Avalanche) 指因为某些原因导致大量的缓存在同一时间内几乎同时失效,进而引起瞬间爆发式的流量冲击后端服务的情况。为了避免此类事件的发生,建议采取如下预防手段: - **分片处理**:将不同类型的业务按照一定规则划分成多个独立的小模块各自维护自己的私有缓存区域,以此降低全局性灾难发生的可能性[^2]。 - **引入熔断保护机制**:一旦检测到异常高峰负载则自动启动紧急预案——如暂停部分非核心功能直至恢复正常运作为止。 --- #### 流量控制(Limiting Flow Control) 通过配置网关层面的限流组件(例如Nginx或Spring Cloud Gateway中的Rate Limit插件),可以有效地限制单位时间内允许通过的最大请求数目,从而减轻上游服务器的压力并提高系统的整体稳定性。 ```yaml spring: cloud: gateway: routes: - id: rate_limit_route uri: lb://example-service predicates: - name: RequestRateLimiter args: redis-rate-limiter.replenishRate: 10 redis-rate-limiter.burstCapacity: 20 ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值