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

相关推荐
用户094248568034 小时前
第19章:库存扣减与秒杀系统:Redis 扛峰值流量
redis·架构·源码
爱写Bug的浮游4 小时前
RTA高频请求下的Redis短缓存工程闭环
redis
没有气的汽水4 小时前
redis加了个分片,把线上写挂了一个多小时
redis
lsc_blog4 小时前
年底还在招的岗位,先确认缺口和到岗要求
git·缓存·ci/cd·职场和发展·github
xixiaoyunya5 小时前
Redis缓存三大问题实战:穿透、击穿、雪崩,我用一个电商秒杀场景讲清楚
数据库·redis·缓存
鶴哥只手遮天7 小时前
《Redis 8.10.3 深度剖析》系列 · 第 1 章·全景与源码地图:版本谱系、构建体系、全局状态与启动链路
redis·后端·开源
70后写java的老码农7 小时前
docker-compose 部署 Redis Stack Server 6.2.6-v19
redis·docker
小南家的青蛙8 小时前
Redis监控与告警体系化实践
redis·监控方案·告警方案
我不会起名字3228 小时前
Redis 从 0 到 1(二十七):JSON 体积是 Protobuf 的 3.7 倍?3 种序列化的 Go 基准测试
redis·go·序列化·protobuf·messagepack