Reids缓存穿透、击穿、雪崩

Redis 缓存异常深度解析:穿透、击穿、雪崩

在分布式系统中,缓存层(Redis)位于应用层与数据库层(DB)之间,其核心作用是分担数据库的读取压力。当缓存机制失效或被绕过时,会引发三种典型的性能危机。


一、 缓存穿透 (Cache Penetration)

定义:

缓存穿透是指查询一个根本不存在的数据。由于缓存不命中,请求会直接打到数据库,而数据库也查询不到结果,因此无法回写缓存。这类请求如果高频发生,数据库将面临宕机风险。

根本原因:

  • 恶意攻击(查询非法 ID)。

  • 业务逻辑漏洞或数据同步延迟。

解决方案:

  1. 缓存空对象 (Negative Caching):

    当数据库返回空结果时,依然将该空结果(或特定错误码)存入 Redis,并设置一个较短的过期时间(如 3-5 分钟)。

  2. 布隆过滤器 (Bloom Filter):

    在请求到达缓存之前,通过布隆过滤器拦截非法请求。布隆过滤器占用内存极小,能判断"该 Key 是否一定不存在"。

Go 语言代码示例(缓存空对象):

复制代码
func GetData(ctx context.Context, key string) (string, error) {
    // 1. 查询缓存
    val, err := rdb.Get(ctx, key).Result()
    if err == redis.Nil {
        // 2. 缓存缺失,查询数据库
        data, dbErr := db.Query(key)
        if dbErr == sql.ErrNoRows {
            // 3. 数据库也不存在,缓存空值防止穿透
            rdb.Set(ctx, key, "", 5*time.Minute)
            return "", errors.New("data not found")
        }
        return data, dbErr
    }
    return val, err
}

二、 缓存击穿 (Cache Breakdown)

定义:

缓存击穿是指一个热点 Key(在极高并发下被访问)在过期的瞬间,海量请求同时发现缓存失效,从而全部涌向数据库。

区别点:

击穿针对的是"单个热点 Key",而穿透针对的是"不存在的数据"。

解决方案:

  1. 互斥锁 (Mutex Lock):

    只有第一个请求能获得锁并去查询数据库,其他请求等待锁释放后重新读取缓存。

  2. 逻辑过期:

    在 Redis 的 Value 中存储过期时间,而不是利用 Redis 自带的 TTL。发现逻辑过期后,异步启动线程更新缓存。

Go 语言代码示例(使用 singleflight 抑制击穿):

在 Go 官方库中,golang.org/x/sync/singleflight 是处理击穿的最佳工具,它能确保同一时刻只有一个请求在执行。

复制代码
var g singleflight.Group

func GetDataWithLock(key string) (string, error) {
    // 1. 尝试读缓存
    val, _ := rdb.Get(ctx, key).Result()
    if val != "" {
        return val, nil
    }

    // 2. 使用 singleflight 合并并发请求
    v, err, _ := g.Do(key, func() (interface{}, error) {
        // 二次检查缓存 (Double Check)
        val, _ := rdb.Get(ctx, key).Result()
        if val != "" {
            return val, nil
        }
        // 查询数据库
        data, _ := db.Query(key)
        rdb.Set(ctx, key, data, 10*time.Minute)
        return data, nil
    })

    return v.(string), err
}

三、 缓存雪崩 (Cache Avalanche)

定义:

缓存雪崩是指在同一时间段内,大量缓存 Key 集中失效 ,或者 Redis 服务集群整体宕机,导致原本由缓存承载的压力全部转移到数据库上。

根本原因:

  • 设置了相同的过期时间(TTL)。

  • Redis 节点崩溃。

解决方案:

  1. 过期时间随机化 (Jitter):

    在基础过期时间上增加一个随机偏移量(如 TTL = BaseTTL + RandomOffset),防止数据同时失效。

  2. 多级缓存:

    引入本地缓存(如 Go-Cache, FreeCache),即使 Redis 宕机,本地缓存仍能支撑部分流量。

  3. 熔断限流:

    当数据库压力过载时,直接拒绝部分请求或返回降级数据,保护核心链路。

Go 语言逻辑实现(随机过期):

复制代码
// 设置缓存时,增加随机扰动
baseTTL := 30 * time.Minute
randomOffset := time.Duration(rand.Intn(300)) * time.Second
rdb.Set(ctx, key, value, baseTTL + randomOffset)

知识总结对比表

特性 缓存穿透 缓存击穿 缓存雪崩
触发条件 查询不存在的数据 热点 Key 到期失效 大量 Key 同时到期或 Redis 故障
核心风险 数据库空转压力 瞬间高并发压垮数据库 数据库整体瘫痪
预防手段 布隆过滤器、空值缓存 互斥锁 (SingleFlight) 随机过期、熔断降级
针对对象 非法/不存在的数据 单个高价值数据点 大规模数据集/基础设施

在实践中,建议将以上策略封装在中间件或统一的数据层(Repository 层)中。对于高并发场景,应优先使用 singleflight 解决击穿问题,并始终为所有缓存 Key 加上 rand.Float64() 生成的偏移时间以规避雪崩风险。

相关推荐
空中海2 小时前
Redis 专家实战:生产架构设计 × 容量规划 × 安全治理 × 37道高频面试题全解
数据库·redis·安全
志飞3 小时前
springboot配置可持久化本地缓存ehcache
java·spring boot·缓存·ehcache·ehcache持久化
下地种菜小叶4 小时前
行为采集、召回、排序、缓存怎么配合?一次讲透
缓存
chen_ever5 小时前
Redis详解|从基础到面试高频题
数据库·redis·后端·缓存
m0_737539376 小时前
redis的安装
数据库·redis·缓存
yuezhilangniao7 小时前
Redis 哨兵高可用集群完整文档-容器部署reids集群
数据库·redis·缓存
Devin~Y7 小时前
大厂Java面试实战:Spring Boot/Cloud + Redis/Kafka + K8s + RAG/Agent 追问全流程(小Y翻车记)
java·spring boot·redis·spring cloud·kafka·kubernetes·micrometer
m0_737539377 小时前
Redis安装与常用命令
数据库·redis·bootstrap
rleS IONS8 小时前
Redis五种用途
数据库·redis·缓存
空中海9 小时前
Redis 原理深度解析:持久化 × 主从复制 × Sentinel × Cluster × 性能排查全攻略
数据库·redis·sentinel