Redis的击穿、雪崩、穿透

Redis的击穿、雪崩、穿透

正常使用redis的流程是:收到请求后,会去redis中查找key,查不到再去数据库中找,在数据库中找到之后就返回并放到缓存当中,查不到就抛出异常。但是在这个过程中会出现一些意外情况,比如击穿、雪崩、穿透。

1、缓存击穿

1、击穿是什么?

高并发状态下,当一个非常热点的key,在某一时间突然失效(或者一个冷门的key突然被大量访问,比如双十一交易量暴增,微博上明星的热点事件等等),大量并发穿破缓存访问数据库,会对数据库造成巨大压力,并且大量线程将key放回redis中,也会造成性能浪费。

2、如何解决?

两种方法:设置过期时间和加锁。

1、设置过期时间

比如对日常访问量答的key设置永不过期,冷门key设置一个过期时间。

但是这个方法解决不了冷门key突然被大量访问的问题。

2、加锁

当再redis中查询不到key的时候,加一个同步锁,只有一个线程能访问到。这个线程将key放进redis之后,其他线程被释放。

代码:

import Redis
import MySQL
import threading
# Redis.redis 是一个定义好的python访问r饿dis数据库的类

lock = threading.Lock()

def func(key):
    res1 = Resdis.redis.read(key)
    if res1 == None:
        with lock():    # 加同步锁
            res2 = Resdis.redis.read(key)    # 再次取数据
            if res2 != None:
                return res2
            res3 = MySQL.read(key)   # 去数据库取数据
            if res3 == None:
                return None      # 取不到说明数据库中没有,抛出异常
            Redis.redis.write(key, res3)
            return res3				 # 取到返回数据并放进redis
    else:
        return res1
1、为什么加锁之后要再访问一次redis?

当第一个线程将了key放进redis之后,后面的线程锁随即释放,如果这里不访问redis而是直接访问数据库,依旧会对数据库造成巨大压力,这里再次访问redis可以立即拿到数据,不需要访问数据库。

2、为什么是同步锁?

如果部署了集群,假如是10个缓存,那么对于数据库来说,应对10个请求是没问题的。

分布式锁可以更好,但同步锁足够解决问题。

2、缓存雪崩

1、雪崩是什么?

与击穿不同,雪崩是指数据库中所有数据某一时刻全部过期或者缓存服务器宕机,导致大量请求访问数据库,给数据库造成巨大压力的情况。

2、如何解决?

1、针对所有key同一时间全部失效的情况,可以在解决击穿的基础上,对key设置随机过期时间。

伪代码:

expire = int(random.random() * 1000)    # 生成一个三位数的随机数
Redis.redis.write(key, res3, expire)

2、针对服务器突然宕机或断电的情况,需要提前做好部署,集群哨兵模式或者多机房。

3、缓存穿透

1、什么是穿透?

请求的数据缓存中没有,数据库也没有。比如有攻击者使用很大的id或者负数id发送大量请求的时候,会对数据库造成巨大压力。

2、如何解决?

三种方式,一种是参数校验,一种是缓存空对象,一种是布隆过滤器。

数据校验

收到请求后对参数做个校验,过滤掉无效参数,比如很大的id或者负数id。

但是这种方法的缺点是没办法判断很大的id是恶意攻击还是正常id恰好没有。

缓存空对象

就是在去数据库查询的时候,不管有没有查到数据,都存在redis中,没有查到就存一个空对象,可以一定程度缓解数据库短时间内被大量攻击的情况。

布隆过滤器

布隆过滤器是一种比列表、集合等更高效的数据结构。

思路:在redis之前设置一个过滤器。每次访问前先去过滤器查询,查询不到再去数据库查询,数据库中也没有就将数据放进过滤器,再次访问在过滤器中访问到,返回空值。这个方法被称为黑名单过滤器,即存储数据库中没有的key。

还有一种白名单过滤器,就是将数据库中存在的数据放进过滤器。请求过来,如果过滤器中存在,再进行后续操作,如果过滤器中不存在,直接返回空值。

伪代码:

bloomFiliter = []   

# 黑名单
def getResquest(key):
    if key in bloomFiliter:
        return None
    return func(key)

# 白名单
def getResquest(key):
    if key in bloomFiliter:
        return func(key)
    return None

# 布隆过滤器不是列表,是一种数据结构,这里是用列表表示布隆过滤器解决缓存穿透的思路。

4、一句话总结

把redis比喻为防弹衣,MySQL比喻为身体,穿透就是子弹穿透了防弹衣打在身体上,雪崩就是很多颗子弹透过防弹衣在身体上爆炸了,穿透就是子弹把防弹衣和身体都打穿了。

伪代码
import Redis
import MySQL
import threading
import random
# Redis MySQL 定义好的python访问数据库的类

lock = threading.Lock()   # 线程锁

def findKey(key):
    res1 = Redis.redis.read(key)		# 第一次从redis中查询数据
    if res1 == None:
        with lock():		# 没查询到,加线程锁,防止大量请求访问数据库
            res2 = Redis.read(key)		# 再次查询,防止阻塞的请求大量访问数据库,这两行可以防止缓存击穿问题
            if res2 == None:
                res3 = MySQL.select(key)
                expire = int(random.random() * 1000)
                Redis.write(key, res3, expire)
                #  不管从数据库查询的结果如何,都存入redis,并设置过期时间,可以防止缓存雪崩问题
                return res3
            else:
                return res2
    else:
    	return res1		# 查询到,直接将数据返回
    
            

bloomFilter = []		#此处以黑名单为例
def bloomFilterGetID(key):
    if key in bloomFilter:
        return None		# id 存在于黑名单,直接返回,可以防止缓存穿透问题
    return findKey(key)
    
### Redis击穿穿透雪崩的解决方案及预防措施 #### 1. **缓存穿透** ##### 定义 缓存穿透是指查询一个既不存在于缓存又不存在于数据库中的键值。由于缓存中没有对应的记录,每次请求都会直接访问数据库,造成数据库压力增大[^2]。 ##### 解决方案 - **布隆过滤器** 使用布隆过滤器可以在请求到达缓存之前进行拦截。布隆过滤器是一种概率型数据结构,能够高效判断某个元素是否存在于集合中。对于不存在的键值,布隆过滤器可以提前拦截并返回固定值,避免对缓存数据库的压力[^4]。 - **设置默认值** 当发现某条数据不存在时,在缓存中为其设置一个空值(如 `null` 或者自定义占位符),并设定较短的有效时间。这样后续对该键的相同请求可以直接从缓存中读取到空值而无需再次查询数据库[^3]。 --- #### 2. **缓存击穿** ##### 定义 缓存击穿指的是某一热点数据恰好在缓存失效的一瞬间收到大量并发请求,此时所有请求都会落到数据库上,形成瞬时高负载,甚至可能压垮数据库[^1]。 ##### 解决方案 - **加锁机制** 在缓存失效时引入分布式锁(如 Redis 的 SETNX 指令)。第一个获取到锁的线程负责加载数据并将结果写回缓存,其余线程则阻塞等待或稍后再试。这种方式可以有效防止多个线程同时向数据库发起请求。 ```python import time import redis r = redis.Redis() def get_with_lock(key, load_func): mutex_key = f"lock:{key}" if r.setnx(mutex_key): # 尝试创建锁 r.expire(mutex_key, 10) # 设置锁有效期 data = load_func() # 加载数据 r.setex(key, 60 * 60, data) # 写入缓存 r.delete(mutex_key) # 删除锁 return data else: time.sleep(0.1) # 等待一段时间后重试 return r.get(key) ``` - **预热缓存** 对于已知的高频访问数据,可在系统启动阶段预先加载至缓存中,并定期刷新其 TTL 时间,确保始终可用。 --- #### 3. **缓存雪崩** ##### 定义 缓存雪崩通常指大批量缓存在同一时刻集中过期,导致短时间内大量请求直达数据库,引发服务不可用的情况。 ##### 解决方案 - **随机化过期时间** 避免批量设置相同的缓存过期时间,而是为每个缓存项分配一定范围内的随机过期时间。例如,原本计划让一条数据存活一小时,改为在一小时内均匀分布到期时间[^1]。 ```python import random TTL_BASE = 3600 # 基础生存时间为1小时 random_ttl = TTL_BASE + random.randint(-300, 300) # ±5分钟浮动 r.setex(key, random_ttl, value) ``` - **异步更新策略** 在缓存即将过期前主动触发后台任务对其进行刷新,而不是等到用户请求到来才重新加载数据。这种方法能够在一定程度上缓解因集体过期带来的冲。 --- #### 总结 通过对 Redis 中存在的三大典型问题——缓存穿透击穿雪崩采取针对性措施,可以显著提升系统的稳定性和可靠性。合理运用技术手段(如布隆过滤器、分布式锁等)以及优化设计思路(如随机化过期时间和预热缓存),有助于构建更加健壮高效的缓存架构。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值