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() 生成的偏移时间以规避雪崩风险。

相关推荐
ward RINL5 小时前
Redis 安装及配置教程(Windows)【安装】
数据库·windows·redis
Nontee7 小时前
Redis高可用架构解析
数据库·redis·架构
m0_612535998 小时前
redis入门到精通
数据库·redis·缓存
刘~浪地球8 小时前
Redis 从入门到精通(三):键操作命令详解
数据库·redis·缓存
刘~浪地球10 小时前
Redis 从入门到精通(四):字符串操作详解
数据库·redis·缓存
xhuiting11 小时前
Redis专题(二)
redis·缓存
一叶飘零_sweeeet11 小时前
Redis 不止缓存!从零到一吃透 Redis 向量数据库
redis·向量数据库
softshow102614 小时前
SpringCloud Redis与分布式
redis·分布式·spring cloud
zlp199214 小时前
软考(系统架构师)-案例分析之Redis与缓存
redis·缓存·软考高级·软考·系统架构师