Redis-雪崩、穿透和击穿(一)

本文探讨了缓存雪崩、穿透及击穿等常见问题,并提供了详细的解决方案,包括高可用架构设计、限流降级策略及热点数据处理方法。

缓存雪崩
对于系统 A,假设每天高峰期每秒 5000 个请求,本来缓存在高峰期可以扛住每秒 4000 个请求,但是缓存机器意外发生了全盘宕机。缓存挂了,此时 1 秒 5000 个请求全部落数据库,数据库必然扛不住,它会报一下警,然后就挂了。此时,如果没有采用什么特别的方案来处理这个故障,DBA 很着急,重启数据库,但是数据库立马又被新的流量给打死了。

这就是缓存雪崩。

在这里插入图片描述

大约在 3 年前,国内比较知名的一个互联网公司,曾因为缓存事故,导致雪崩,后台系统全部崩溃,事故从当天下午持续到晚上凌晨 3~4 点,公司损失了几千万。

缓存雪崩的事前事中事后的解决方案如下。

事前:redis 高可用,主从+哨兵,redis cluster,避免全盘崩溃。
事中:本地 ehcache 缓存 + hystrix 限流&降级,避免 MySQL 被打死。
事后:redis 持久化,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据。

在这里插入图片描述
用户发送一个请求,系统 A 收到请求后,先查本地 ehcache 缓存,如果没查到再查 redis。如果 ehcache 和 redis 都没有,再查数据库,将数据库中的结果,写入 ehcache 和 redis 中。

限流组件,可以设置每秒的请求,有多少能通过组件,剩余的未通过的请求,怎么办?走降级!可以返回一些默认的值,或者友情提示,或者空白的值。

好处:

数据库绝对不会死,限流组件确保了每秒只有多少个请求能通过。
只要数据库不死,就是说,对用户来说,2/5 的请求都是可以被处理的。
只要有 2/5 的请求可以被处理,就意味着你的系统没死,对用户来说,可能就是点击几次刷不出来页面,但是多点几次,就可以刷出来一次。
缓存穿透
对于系统A,假设一秒 5000 个请求,结果其中 4000 个请求是黑客发出的恶意攻击。

黑客发出的那 4000 个攻击,缓存中查不到,每次你去数据库里查,也查不到。

举个栗子。数据库 id 是从 1 开始的,结果黑客发过来的请求 id 全部都是负数。这样的话,缓存中不会有,请求每次都“视缓存于无物”,直接查询数据库。这种恶意攻击场景的缓存穿透就会直接把数据库给打死。
在这里插入图片描述

解决方式很简单,每次系统 A 从数据库中只要没查到,就写一个空值到缓存里去,比如 set -999 UNKNOWN。然后设置一个过期时间,这样的话,下次有相同的 key 来访问的时候,在缓存失效之前,都可以直接从缓存中取数据。

缓存击穿
缓存击穿,就是说某个 key 非常热点,访问非常频繁,处于集中式高并发访问的情况,当这个 key 在失效的瞬间,大量的请求就击穿了缓存,直接请求数据库,就像是在一道屏障上凿开了一个洞。

解决方式也很简单,可以将热点数据设置为永远不过期;或者基于 redis or zookeeper 实现互斥锁,等待第一个请求构建完缓存之后,再释放锁,进而其它请求才能通过该 key 访问数据。

简洁版
缓存穿透
概念
访问一个不存在的key,缓存不起作用,请求会穿透到DB,流量大时DB会挂掉。

解决方案
采用布隆过滤器,使用一个足够大的bitmap,用于存储可能访问的key,不存在的key直接被过滤;
访问key未在DB查询到值,也将空值写进缓存,但可以设置较短过期时间。
缓存雪崩
概念
大量的key设置了相同的过期时间,导致在缓存在同一时刻全部失效,造成瞬时DB请求量大、压力骤增,引起雪崩。

解决方案
可以给缓存设置过期时间时加上一个随机值时间,使得每个key的过期时间分布开来,不会集中在同一时刻失效。

缓存击穿
概念
一个存在的key,在缓存过期的一刻,同时有大量的请求,这些请求都会击穿到DB,造成瞬时DB请求量大、压力骤增。

解决方案
在访问key之前,采用SETNX(set if not exists)来设置另一个短期key来锁住当前key的访问,访问结束再删除该短期key。

### Redis 雪崩穿透击穿现象及其区别 Redis 在作为缓存系统时,可能会遇到雪崩穿透击穿三种问题。以下是这三种问题的定义、原因及解决方案。 #### 1. 缓存雪崩 **定义**:多个缓存 key 同时失效,导致大量请求直接访问数据库,从而造成数据库压力过大甚至崩溃[^2]。 **原因**: - 缓存中的多个 key 设置了相同的过期时间。 - 系统重启或缓存服务器宕机后,所有 key 的缓存同时失效。 **解决方案**: - 为不同的 key 设置随机的过期时间,避免集中失效[^2]。 - 使用永不过期的缓存,并通过后台线程定期更新缓存内容。 - 引入布隆过滤器或其他预判机制,减少无效查询对数据库的压力。 ```python # 示例:设置随机过期时间 import random def set_key_with_random_expiry(key, value): expiry_time = 60 + random.randint(1, 30) # 随机增加 1-30 秒 redis.set(key, value, ex=expiry_time) ``` --- #### 2. 缓存穿透 **定义**:某些不存在的 key 被频繁查询,每次查询都会穿透到数据库,导致数据库负载过高[^3]。 **原因**: - 查询的 key 本身不存在于缓存数据库中。 - 恶意攻击者故意构造不存在的 key 进行高频查询。 **解决方案**: - 在缓存中存储空值(例如 `null` 或 `""`),并设置较短的过期时间,避免后续重复查询数据库[^2]。 - 使用布隆过滤器提前判断 key 是否存在,减少无效查询。 ```python # 示例:缓存空值 def get_value_from_cache_or_db(key): value = redis.get(key) if value is None: value = db.query(key) if value is None: redis.set(key, "null", ex=60) # 缓存空值,过期时间为 60 秒 else: redis.set(key, value) return value ``` --- #### 3. 缓存击穿 **定义**:某个热点 key 在缓存失效瞬间被大量并发请求访问,导致这些请求全部落到数据库上[^3]。 **原因**: - 热点 key 的访问频率极高。 - 缓存失效后,大量并发请求同时查询数据库。 **解决方案**: - 使用互斥锁(mutex)机制,确保只有个线程查询数据库,其他线程等待结果[^3]。 - 设置永不过期的缓存,并通过后台线程定期更新数据。 ```python # 示例:使用双重检索机制 import threading lock = threading.Lock() def get_value_with_mutex(key): value = redis.get(key) if value is None: with lock: value = redis.get(key) # 再次检查,确保只有个线程查询数据库 if value is None: value = db.query(key) redis.set(key, value, ex=1000) # 更新缓存 return value ``` --- ### 总结 - **缓存雪崩**:多个 key 同时失效,解决方案是设置随机过期时间引入永不过期缓存。 - **缓存穿透**:查询不存在的 key,解决方案是缓存空值或使用布隆过滤器。 - **缓存击穿**:热点 key 缓存失效时被大量请求访问,解决方案是使用互斥锁或永不过期缓存[^3]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值