缓存穿透和缓存击穿

一、缓存处理流程

     正常的缓存处理流程图:

      

 

二、缓存穿透

       描述:

       缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。(即:恶意的大面积请求不存在的数据)

      解决方案:

1.初级方案

对请求的接口参数进行校验,减少取数据库查询;只适合已知的可控的数据。

2.空值缓存

将不存在的数据在缓存中定义为null或"",具体业务具体设置。这样的话,可以解决短期内查询这个key的时候,不用去数据库查询。
需要强调注意:为了系统的最终一致性,这些key必须设置过期时间,或者必须存在更新方式,防止这个key的数据后期真实存在,但改key始终为空,导致数据不一致的情况出现。

不好的地方:如果大面积分散的无规律请求,会占用大量的缓存空间。并且不能抗住瞬时流量冲击(尤其是遇到恶意的攻击的时候,有可能将缓存空间打爆,影响范围更大),需要额外配置降级开关(查询数据库的开关或者限流),这时本方案就显得没想象的那么美好。针对不能抗住瞬时流量的情况,常见的处理方式是使用计数器,对不存在的key进行计数,当某个key在一定时间达到一定的量级,就查询一次数据库,按照数据库的返回值对key进行缓存。未达指定阈值数量之前,按照商定的空值返回。

建议使用场景为:key全集数据数据量级较小,并且完全可预测,可以通过提前填充的方式直接将数据缓存。

3.布隆过滤器(BloomFilter)

布隆过滤器是一种数据结构,比较巧妙的概率型数据结构(probabilistic data structure),特点是高效地插入和查询,可以用来告诉你 “某样东西一定不存在或者可能存在”。相比于传统的 List、Set、Map 等数据结构,它更高效、占用空间更少,但是缺点是其返回的结果是概率性的,而不是确切的。实际应用中,Google BigTable,Apache HBbase 和 Apache Cassandra 使用布隆过滤器减少对不存在的行和列的查找。

布隆过滤器的原理如下:

redis-缓存穿透和缓存击穿处理

 

假如“京东”经过hash后占位为618,加入后,如下所示:

redis-缓存穿透和缓存击穿处理

 

假如大促经过hash后占位为为678,加入后,如下所示:

redis-缓存穿透和缓存击穿处理

 

假如某宝hash后占位256,则可以完全判断该key不存在,直接返回null即可。但某某多hash后占位167,则不能判断是否存在,需要进行查库操作进行判断。

这里需要强调注意的是必须使用高效的hash算法,否则这种方式会严重影响系统的性能,建议的算法包括MurmurHash、Fnv的稳定高效的算法。

使用过程中,通常把有数据的key都放到BloomFilter中,每次查询的时候都先去BloomFilter判断,如果没有就直接返回空。由于布隆过滤器不支持删除操作(具体结合上图推算删除一个就可以得知),对于删除的key,查询就会经过BloomFilter然后查询缓存再查询数据库,所以BloomFilter建议结合缓存空值用,对于删除的key,可以在缓存中缓存空。(当然有同学自行实现了可删除的布隆过滤器——Counting Bloom Filter,原理较为简单,本文不做具体分析,个人感觉将简单的问题复杂化了)。

同样它也不支持扩容操作,这就要求布隆过滤器建立初期必须进行严格的推算,确保后期不需要扩容,否则重建布隆过滤器的成本可能会超乎想象。具体的推算公式如下(具体的推算过程非本文重点,请各位自行查找):

redis-缓存穿透和缓存击穿处理

 

k 为哈希函数的个数,m 为布隆过滤器的长度,n 为插入元素的个数(需要处理的数据个数),p 为误报率。

布隆过滤器的使用场景受key的状态限制,如果key是动态无规律的,不建议使用该方式。作者使用布隆过滤器作为用户是否参与活动的过滤,布隆过滤器在活动期间最大值为目前的会员数量,完全可控。

考虑真实情况下,缓存的存储空间及性能问题,在真实使用中,为了避免热key和大key的问题,首先对用户标识进行了Hash,首先对hash按照布隆过滤器的数量进行取余,确定使用哪个布隆过滤器,然后使用布隆过滤器。具体情况如下:

redis-缓存穿透和缓存击穿处理

 

 

三、缓存击穿

      描述:

      缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力

      解决方案:

1、互斥锁(mutex key)

这是比较常见的做法,是在缓存失效的时候,不是立即去查询数据库,先抢互斥锁(比如Redis的SETNX一个mutex key),当操作返回成功时(即获取到互斥锁),再进行查询数据库的操作并回设缓存;否则,就重试整个获取缓存的方法或者直接返回空。SETNX,官方的解释是只在键 key 不存在的情况下,将键 key 的值设置为 value 。若键 key 已经存在,则 SETNX 命令不做任何动作。SETNX 是『SET if Not eXists』(如果不存在,则SET)的简写。

但这种方式显然存在问题,Redis的SETNX命令是当key不存在时设置key,但SETNX不能同时完成EXPIRE设置失效时长,不能保证SETNX和EXPIRE的原子性。这会导致SETNX成功但EXPIRE失败时,锁永远不会释放,下面提供了一种可行的代码,供大家讨论:

redis-缓存穿透和缓存击穿处理

 

Redis还是善解人意的,从 2.6.12 起,我们可以使用SET命令完成SETNX和EXPIRE的操作,并且这种操作是原子操作,可以完全替代上述的代码了。

这种简单粗暴的方式有着严格是使用场景(换句话说就是有着严重的缺陷),如果缓存大量失效(缓存雪崩),那么对于数据库是一场灾难;如果数据库查询缓慢,不仅对数据是一场灾难,对于使用该缓存的接口会造成线程阻塞,接口性能又开启了另外一场灾难!一般情况下,这种方式适用于永久缓存的key,或者key偶尔丢失的情况下,其他情况请各位读者慎重考虑或增加其他机制保护数据库和接口(如使用Hystrix限流&降级等)。

2、异步构建缓存

当缓存失效时,不是立刻去查询数据库,而是先创建缓存更新的异步任务,然后直接返回空值。这种做法不会阻塞当前线程,并且对于数据库的压力基本可控,但牺牲了整体数据的一致性。从实际的使用看,这种方法对于性能非常友好,唯一不足的就是构建缓存时候,所有查询返回的内容均为空值,但是对于一致性要求不高的互联网功能来说这个还是可以忍受。

redis-缓存穿透和缓存击穿处理

3.设置热点数据永远不过期。

redis-缓存穿透和缓存击穿处理

四、缓存雪崩

      描述:

      缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,        缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。

     解决方案:

缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。
设置热点数据永远不过期。
 

<think>好的,我现在需要回答用户关于Redis缓存穿透缓存击穿的问题。首先,我需要回忆这两个概念的定义区别,以及它们的解决方案。根据提供的引用资料,缓存穿透是查询不存在的数据,导致直接访问数据库;缓存击穿则是热点数据过期后,大量请求同时访问数据库。 接下来,我需要确认每个问题的原因。缓存穿透通常由恶意攻或误查询引起,而缓存击穿是因为热点数据突然失效。解决方案方面,缓存穿透可以用布隆过滤器或缓存空值,缓存击穿则可以用互斥锁或永不过期策略。 然后,我需要整理结构,确保回答清晰。先分别解释两者的定义、原因解决方案,再对比它们的区别。要注意使用中文,并正确应用LaTeX格式,比如独立公式用$$...$$,行内用$...$。同时,生成相关问题,并在引用处添加标识。最后检查是否符合用户的所有要求,包括引用正确语法正确。</think>### Redis 缓存穿透缓存击穿对比分析 #### 一、缓存穿透 **定义** 缓存穿透指查询**数据库中不存在**的数据(如无效ID),导致每次请求绕过缓存直接访问数据库。例如:黑客使用不存在的用户ID频繁请求用户信息[^3][^4]。 **原因** 1. 恶意攻:利用无效键发起高频查询 2. 业务误操作:程序缺陷导致生成非法查询条件 **解决方案** 1. **布隆过滤器** 使用数据结构预先存储所有合法键值,查询前进行过滤: $$ P_{\text{误判}} \approx \left(1 - e^{-kn/m}\right)^k $$ 其中$n$为元素数量,$m$为位数组长度,$k$为哈希函数数量[^3]。 2. **缓存空对象** 对无效查询结果缓存空值(设置较短过期时间): ```python def get_data(key): data = redis.get(key) if data is None: data = db.query(key) if data is None: # 数据库不存在 redis.setex(key, 300, "NULL") # 缓存空值5分钟 return None redis.setex(key, 3600, data) return data ``` #### 二、缓存击穿 **定义** 缓存击穿指**热点数据过期**时,大量并发请求同时穿透缓存直达数据库,例如:双十一商品详情页的热门商品缓存失效[^2]。 **原因** 1. 高并发场景下热点数据突然失效 2. 缓存重建耗时过长 **解决方案** 1. **互斥锁(Mutex Lock)** 仅允许一个线程重建缓存,其他线程等待: ```python def get_data(key): data = redis.get(key) if data is None: if redis.setnx("lock_" + key, 1): # 获取锁 data = db.query(key) redis.setex(key, 3600, data) redis.delete("lock_" + key) else: time.sleep(0.1) return get_data(key) # 重试 return data ``` 2. **逻辑过期时间** 数据永久存储,额外字段记录过期时间,异步更新: ```json { "value": "真实数据", "expire_time": 1717027200 # Unix时间戳 } ``` #### 三、核心差异对比 | 维度 | 缓存穿透 | 缓存击穿 | |--------------|-----------------------------------|-----------------------------------| | **数据存在性** | 数据库与缓存均不存在 | 数据库存在,缓存过期[^3] | | **触发条件** | 非法/无效请求 | 热点数据集中失效 | | **攻特征** | 可能伴随恶意攻 | 通常为正常业务流量 | | **防御重心** | 非法请求过滤+减少数据库穿透 | 并发控制+缓存重建优化 |
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值