欢迎光临
我们一直在努力

Redis - 缓存雪崩、击穿、穿透:三大异常的成因与应对

文章目录

  • 引言
  • 缓存雪崩
    • 什么是缓存雪崩
    • 原因一:大量数据同时过期
    • 原因二:Redis 实例宕机
  • 缓存击穿
    • 什么是缓存击穿
    • 典型场景
    • 解决方案:热点数据不设过期时间
  • 缓存穿透
    • 什么是缓存穿透
    • 和雪崩、击穿的本质区别
    • 发生原因
    • 解决方案一:缓存空值或缺省值
    • 解决方案二:布隆过滤器
    • 解决方案三:前端请求检测
  • 三大问题对比
  • 预防优于治疗
  • 总结

在这里插入图片描述

引言

缓存和数据库的数据不一致是个棘手问题,但至少数据还在。更严重的情况是:大量请求直接绕过缓存打到数据库上,数据库扛不住直接宕机。

缓存雪崩、缓存击穿、缓存穿透,这三个问题的共同后果都是数据库压力激增。但它们的成因不同,应对方案也不同。搞清楚三者的区别和各自的解决思路,是 Redis 缓存运维的必备技能。

缓存雪崩

什么是缓存雪崩

在这里插入图片描述

大量请求无法在 Redis 中处理,全部涌向数据库,导致数据库压力激增甚至宕机。

原因一:大量数据同时过期

如果给一批数据设置了相同的过期时间,到期后这些数据同时失效。此时所有访问这些数据的请求都会发生缓存缺失,全部回源到数据库。

举个例子:电商平台在晚上 8 点做活动,提前把商品数据缓存起来,统一设置 2 小时过期。到了晚上 10 点,所有缓存同时失效,瞬间大量请求打到数据库。

解决方案一:过期时间加随机值

给每个数据的过期时间加一个小的随机数(比如 1~3 分钟),让数据在相近但不完全相同的时间点过期,避免集中失效。

# 基础过期时间 2 小时,加上 1~180 秒的随机值
expire_time = 7200 + random.randint(1, 180)
redis.setex(key, expire_time, value)

解决方案二:服务降级

发生雪崩时,区分核心数据和非核心数据:

  • 非核心数据(如商品属性、推荐信息):暂停从缓存查询,直接返回预定义信息、空值或错误提示
  • 核心数据(如库存、价格):仍然允许查询缓存,缓存缺失时也允许查数据库

这样只有核心数据的请求会到达数据库,压力大幅降低。

原因二:Redis 实例宕机

Redis 实例故障后,所有请求都无法被缓存处理,全部压到数据库。一个 Redis 实例能支撑数万级 QPS,而单个数据库可能只能支撑数千级 QPS,压力差近十倍。

解决方案一:服务熔断

发现 Redis 宕机后,缓存客户端不再把请求发给 Redis,直接返回错误。等 Redis 恢复后再放行请求。

熔断保护了数据库不被压垮,但代价是整个缓存系统暂停服务,业务影响范围大。

在这里插入图片描述

解决方案二:请求限流

在请求入口前端控制每秒进入系统的请求数。比如正常情况下每秒 1 万请求,9000 个走缓存,1000 个到数据库。Redis 宕机后,把入口限制为每秒 1000 个请求,多余的直接拒绝。

限流比熔断温和,至少还有部分请求能正常处理。

在这里插入图片描述

解决方案三:事前预防——高可用集群

通过主从节点 + 哨兵机制构建 Redis 高可用集群。主节点宕机后,从节点自动切换为主节点继续服务,从根本上避免因单点故障导致的雪崩。

缓存击穿

什么是缓存击穿

在这里插入图片描述

某个热点数据的缓存过期失效,大量访问该数据的请求瞬间全部打到数据库。

和雪崩的区别:雪崩是大量数据同时失效,击穿是单个热点数据失效。但如果这个热点数据的访问量足够大,效果同样致命。

典型场景

微博热搜话题的缓存过期、秒杀商品信息的缓存过期、热门直播间数据的缓存过期。

解决方案:热点数据不设过期时间

对于访问特别频繁的热点数据,直接不设置过期时间。Redis 数万级的吞吐量完全能应对大量并发请求。

# 热点数据不设过期时间
redis.set("hot_product_12345", product_info)

# 普通数据设置过期时间
redis.setex("normal_product_67890", 3600, product_info)

数据更新时,通过应用程序主动更新缓存值,而不是依赖过期后重新加载。

缓存穿透

什么是缓存穿透

在这里插入图片描述

请求的数据既不在 Redis 中,也不在数据库中。缓存缺失后查数据库,数据库也没有,无法回写缓存。后续相同请求继续穿透,缓存形同虚设。

和雪崩、击穿的本质区别

  • 雪崩和击穿:数据在数据库中存在,只是缓存暂时没有。一旦数据被重新加载到缓存,问题就解决了
  • 穿透:数据在数据库中也不存在,缓存永远无法被填充,问题会持续存在

发生原因

  • 业务误操作:缓存和数据库中的数据被误删除
  • 恶意攻击:故意构造数据库中不存在的 key 进行大量请求
  • 解决方案一:缓存空值或缺省值

    发现数据库中也没有数据时,在 Redis 中缓存一个空值或业务约定的缺省值。后续请求直接从缓存返回,不再穿透到数据库。

    value = redis.get(key)
    if value is not None:
    return value

    # 缓存缺失,查数据库
    db_value = db.query(key)
    if db_value is not None:
    redis.setex(key, 3600, db_value)
    return db_value
    else:
    # 数据库也没有,缓存空值,设置较短过期时间
    redis.setex(key, 300, "")
    return None

    注意给空值设置较短的过期时间,避免后续数据真正写入数据库后,缓存中的空值长期阻止正常访问。

    解决方案二:布隆过滤器

    在这里插入图片描述

    布隆过滤器(Bloom Filter)可以快速判断一个数据是否存在,避免无效的数据库查询。

    工作原理:

    布隆过滤器由一个初值全为 0 的 bit 数组和 N 个哈希函数组成。

    标记数据存在时:

  • 用 N 个哈希函数计算数据的 N 个哈希值
  • 对 bit 数组长度取模,得到 N 个位置
  • 把这 N 个位置的 bit 设为 1
  • 查询数据是否存在时:

  • 同样计算 N 个位置
  • 检查这 N 个位置的 bit 值
  • 只要有一个为 0,数据一定不存在
  • 示例:bit 数组长度 10,3 个哈希函数
    标记数据 X:hash1(X)%10=1, hash2(X)%10=3, hash3(X)%10=7
    数组:[0,1,0,1,0,0,0,1,0,0]

    查询数据 Y:hash1(Y)%10=1, hash2(Y)%10=4, hash3(Y)%10=7
    位置 4 的值为 0 → Y 一定不存在

    使用方式:

    数据写入数据库时,同时在布隆过滤器中标记。缓存缺失后,先查布隆过滤器:

    • 布隆过滤器判断不存在 → 直接返回,不查数据库
    • 布隆过滤器判断可能存在 → 查数据库

    布隆过滤器有一定的误判率(判断存在时可能实际不存在),但判断不存在时一定不存在。误判只会导致少量无效的数据库查询,不影响正确性。

    Redis 4.0 以后提供了布隆过滤器模块,可以直接使用。

    解决方案三:前端请求检测

    在请求入口对参数进行合法性校验,过滤掉明显的恶意请求:

    • 参数格式不合法(如 ID 为负数)
    • 参数值明显超出业务范围
    • 请求字段缺失

    把恶意请求拦截在最外层,根本不让它们进入缓存和数据库。

    三大问题对比

    问题数据在数据库中触发条件核心解决思路
    雪崩 存在 大量数据同时过期 / Redis 宕机 分散过期时间 / 高可用集群 / 熔断限流
    击穿 存在 单个热点数据过期 热点数据不设过期时间
    穿透 不存在 查询不存在的数据 缓存空值 / 布隆过滤器 / 前端拦截

    预防优于治疗

    服务熔断、服务降级、请求限流都是"有损"方案——保住了数据库,但牺牲了业务体验。真正应该做的是事前预防:

    • 防雪崩:过期时间加随机值 + Redis 高可用集群
    • 防击穿:热点数据不设过期时间
    • 防穿透:入口前端做恶意请求检测 + 规范数据删除操作避免误删

    总结

    在这里插入图片描述

    三大缓存异常的本质都是"请求无法被缓存拦截,压力传导到数据库"。区别在于原因不同:

    • 雪崩是缓存大面积失效
    • 击穿是热点缓存单点失效
    • 穿透是数据根本不存在

    理解了成因,解决方案就是对症下药。在系统设计阶段就把预防措施做好,比出了问题再熔断限流要好得多。毕竟,最好的故障处理就是不让故障发生。

    在这里插入图片描述

    赞(0)
    未经允许不得转载:171主机测评 » Redis - 缓存雪崩、击穿、穿透:三大异常的成因与应对
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址