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 的安装(详细教程)
数据库·redis·缓存
feasibility.2 小时前
反爬十层妖塔:现代爬虫攻防的立体战争
爬虫·python·科技·scrapy·rust·go·硬件
数据库小学妹2 小时前
数据库连接池避坑指南:告别“连接超时”与“资源耗尽”,让系统跑得更快!
数据库·redis·sql·mysql·缓存·dba
難釋懷3 小时前
Redis网络模型-IO多路复用模型-poll模式
网络·数据库·redis
用户398346161205 小时前
Go-Spring 实战第 1 课 —— 统一配置模型:Properties 与 Path
go
王中阳Go6 小时前
秒杀、分库分表、全链路追踪:一个电商微服务的架构全拆解
后端·go
环流_8 小时前
Redis中string类型的应用场景
数据库·redis·缓存
环流_8 小时前
redis中list类型
数据库·redis·list
漓漾li8 小时前
每日面试题(2026-05-15)
架构·go·agent