Redis缓存雪崩、缓存击穿、缓存穿透和常见的几种缓存模式

本文深入解析缓存雪崩、缓存击穿、缓存穿透的概念及解决方案,探讨常见缓存模式,如CacheAside、Read/WriteThrough和WriteBehind模式的特点与应用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、缓存雪崩

1)、什么是缓存雪崩?

如果缓存集中在一段时间内失效,发生大量的缓存击穿,所有的查询都落在数据库上,造成了缓存雪崩

由于原有缓存失效,新缓存未到期间所有原本应该访问缓存的请求都去查询数据库了,而对数据库CPU和内存造成巨大压力,严重的会造成数据库宕机

2)、有什么解决方案来防止缓存雪崩?

1)加锁排队

mutex互斥锁解决,Redis的SETNX去set一个mutex key,当操作返回成功时,再进行加载数据库的操作并回设缓存,否则,就重试整个get缓存的方法

2)数据预热

缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题。用户直接查询事先被预热的缓存数据。可以通过缓存reload机制,预先去更新缓存,再即将发生大并发访问前手动触发加载缓存不同的key

3)双层缓存策略

C1为原始缓存,C2为拷贝缓存,C1失效时,可以访问C2,C1缓存失效时间设置为短期,C2设置为长期

4)定时更新缓存策略

实效性要求不高的缓存,容器启动初始化加载,采用定时任务更新或移除缓存

5)设置不同的过期时间,让缓存失效的时间点尽量均匀

二、缓存击穿

1)、什么是缓存击穿?

在平常高并发的系统中,大量的请求同时查询一个key时,此时这个key正好失效了,就会导致大量的请求都打到数据库上面去。这种现象我们称为缓存击穿

2)、会带来什么问题

会造成某一时刻数据库请求量过大,压力剧增

3)、如何解决

上面的现象是多个线程同时去查询数据库的这条数据,那么我们可以在第一个查询数据的请求上使用一个互斥锁来锁住它

其他的线程走到这一步拿不到锁就等着,等第一个线程查询到了数据,然后做缓存。后面的线程进来发现已经有缓存了,就直接走缓存

三、缓存穿透

1)、什么是缓存穿透?

缓存穿透是指用户查询数据,在数据库没有,自然在缓存中也不会有。这样就导致用户查询的时候,在缓存中找不到对应key的value,每次都要去数据库再查询一遍,然后返回空(相当于进行了两次无用的查询)。这样请求就绕过缓存直接查数据库

2)、有什么解决方案来防止缓存穿透?

1)缓存空值

如果一个查询返回的数据为空(不管是数据不存在,还是系统故障)我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过5分钟。通过这个设置的默认值存放到缓存,这样第二次到缓存中获取就有值了,而不会继续访问数据库

2)采用布隆过滤器BloomFilter

优势:占用内存空间很小,位存储;性能特别高,使用key的hash判断key存不存在

将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力

在缓存之前在加一层BloomFilter,在查询的时候先去BloomFilter去查询key是否存在,如果不存在就直接返回,存在再去查询缓存,缓存中没有再去查询数据库

四、常见的几种缓存模式

1)、Cache Aside

  • 应用在查询数据的时候,先从缓存Cache中读取数据,如果缓存中没有,则再从数据库中读取数据,得到数据库的数据之后,将这个数据也放到缓存Cache中
  • 如果应用要更新某个数据,也是先去更新数据库中的数据,更新完成之后,则通过指令让缓存Cache中的数据失效

1)这里为什么不让更新操作在写完数据库之后,紧接着去把缓存Cache中的数据也修改了呢?

主要是因为这样做的话,就有2个写操作的事件了,担心在并发的情况下会导致脏数据,举个例子:假如同时有2个请求,请求A和请求B,并发的执行。请求A是要去读数据,请求B是要去更新数据。初始状态缓存中是没有数据的,当请求A读到数据之后,准备往回写的时候,此刻,请求B正好要更新数据,更新完了数据库之后,又去把缓存更新了,那请求A再往缓存中写的就是旧数据了,属于脏数据

2)那么Cache Aside模式就没有脏数据问题了吗?

在极端情况下也可能会产生脏数据。例如,同时有2个请求,请求A和请求B,并发的执行。请求A是要去读数据,请求B是要去写数据。假如初始状态缓存中没有这个数据,那请求A发现缓存中没有数据,就会去数据库中读数据,读到了数据准备写回缓存中,就在这个时候,请求B是要去写数据的,请求B在写完数据库的数据之后,又去设置了缓存失效。这个时候,请求A由于在数据库中读到了之前的旧数据,开始往缓存中写数据了,此时写进入的就也是旧数据。那么最终就会导致,缓存中的数据与数据库的数据不一致,造成了脏数据

2)、Read/Write Through

  • 应用要读数据和更新数据都直接访问缓存服务
  • 缓存服务同步地将数据更新到数据库

出现脏数据的概率较低,但是就强依赖缓存,对缓存服务的稳定性有较大要求

3)、Write Behind模式

  • 应用要读数据和更新数据都直接访问缓存服务
  • 缓存服务异步地将数据更新到数据库(通过异步任务)

速度快,效率会非常高,但是数据的一致性比较差,还可能会有数据的丢失情况,实现逻辑也较为复杂

参考:https://baijiahao.baidu.com/s?id=1609569533593209908&wfr=spider&for=pc
https://mp.weixin.qq.com/s/7gbJCeBKklTlAxU_vsrIxg

### Redis缓存穿透击穿雪崩及其解决方案 #### 缓存穿透 (Cache Penetration) 当查询的数据在数据库中不存在,每次请求都会穿透到数据库层,造成大量无意义的查询压力。这种情况不仅浪费资源,还可能被恶意利用进行攻击。 为了防止这种现象的发生,可以采取如下措施: - **布隆过滤器**:使用布隆过滤器来判断数据是否存在,如果布隆过滤器返回false,则可以直接断定该键一定不存在于缓存与数据库之中[^1]。 - **空对象缓存**:对于确实不存在的数据项,在缓存中存储一个特殊的标记(如`null`),并设置较短的有效期。这样下次再遇到相同的查询时就可以直接命中缓存而无需访问数据库。 ```python import redis r = redis.Redis() def get_data_with_null_object(key): data = r.get(key) if not data: # 假设db_get是从数据库获取数据的方法 db_result = db_get(key) if not db_result: r.setex(key, 60, "NULL") # 设置过期时间为60秒 return db_result elif data.decode('utf-8') == 'NULL': return None else: return data ``` #### 缓存击穿 (Cache Breakdown) 某个热点key突然失效,此时大量的并发请求会瞬间打到数据库上,给数据库带来巨大压力。为了避免这一情况发生,可采用以下方法: - **加锁机制**:通过分布式锁控制同一时间只有一个线程能够更新缓存中的特定条目;其他线程则等待直到新的值已经被加载回缓存为止。 - **设置随机有效期**:为热Key设定带有轻微波动范围的时间戳作为其TTL(Time To Live),从而减少因多个实例同时到期而导致的大规模刷新操作的可能性。 ```python from threading import Lock lock_dict = {} def set_value_with_lock(redis_client, key, value, ttl=None): lock_key = f'lock:{key}' with Lock() as lock: acquired = False try: while True: if not redis_client.exists(lock_key): acquired = bool(redis_client.setnx(lock_key, 1)) if acquired: break time.sleep(0.1) # 尝试获得锁 redis_client.set(key, value, ex=ttl or random.randint(90, 120)) # TTL带有一些随机性 finally: if acquired: redis_client.delete(lock_key) def get_or_set_cache(redis_client, key, fetch_func, ttl=None): result = redis_client.get(key) if result is None: set_value_with_lock(redis_client, key, fetch_func(), ttl) result = redis_client.get(key) return result ``` #### 缓存雪崩 (Cache Avalanche) 由于某些原因导致大量缓存在几乎相同时间内集体失效,进而引发对后端服务的巨大冲击。针对此问题有几种常见处理方式: - **分片策略**:将不同类型的业务逻辑按照一定的规则分配至不同的Redis节点保存,即使部分机器出现问题也不会影响整个系统的正常运作。 - **限流降级**:引入熔断器模式,在极端情况下自动拒绝超出服务能力之外的新请求,保护核心功能不受损害。 ```python class RateLimiter(object): def __init__(self, rate_limit_per_minute): self.rate_limit_per_minute = rate_limit_per_minute self.requests_in_last_min = [] def allow_request(self): current_time = int(time.time()) one_minute_ago = current_time - 60 filtered_requests = list(filter(lambda t: t >= one_minute_ago, self.requests_in_last_min)) if len(filtered_requests) < self.rate_limit_per_minute: self.requests_in_last_min.append(current_time) return True else: return False rate_limiter = RateLimiter(rate_limit_per_minute=100) if rate_limiter.allow_request(): process_user_request() else: respond_with_error("Too many requests.") ```
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

邋遢的流浪剑客

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值