Redis缓存被击穿,使用布隆过滤器加互斥锁双保险

举报
海是岛思念的泪 发表于 2026/09/09 15:52:24 2026/09/09
【摘要】 热点 Key 过期瞬间大量请求直穿数据库,用布隆过滤器挡住不存在的 Key,互斥锁保证只有一个请求查 DB,附 Go 代码实现。

一、击穿和雪崩别搞混

先理清下概念,常见的三种缓存问题如下:

问题 触发条件 影响范围
缓存击穿 单个热点 Key 过期 大量请求打到 DB
缓存雪崩 大批 Key 同时过期 DB 被压垮
缓存穿透 查询不存在的 Key 每次都查 DB

主要讲解下击穿,还有穿透的防护

image.png

二、两道防线

单靠一种手段不够稳,我用了两道防线叠着来:

第一道:布隆过滤器——在请求到 Redis 之前先过一遍,Key 不存在的直接返回,根本不让它往下走。这样既防了穿透,又减少了 Redis 的无效查询。

第二道:互斥锁——缓存未命中时,用 Redis 的 SETNX 加一把锁,拿到锁的那个请求去查 DB 并回写缓存,拿不到锁的等一下再重试读缓存。这样保证同一时间只有一个请求打到 DB。

image.png

三、代码实现

这里用Go和go-redis写个简单的示例:

package main

import (
    "context"
    "time"

    "github.com/redis/go-redis/v9"
    "github.com/bits-and-blooms/bloom/v3"
)

type CacheGuard struct {
    rdb *redis.Client
    bf  *bloom.BloomFilter
}

// Get 先过布隆过滤器,再查缓存,miss 时加互斥锁查 DB
func (cg *CacheGuard) Get(ctx context.Context, key string) (string, error) {
    // 第一道:布隆过滤器
    if !cg.bf.Test([]byte(key)) {
        return "", nil // Key 不存在,直接返回
    }

    // 查 Redis
    val, err := cg.rdb.Get(ctx, key).Result()
    if err == nil {
        return val, nil // 缓存命中
    }

    // 第二道:互斥锁
    lockKey := "lock:" + key
    locked, err := cg.rdb.SetNX(ctx, lockKey, 1, 3*time.Second).Result()
    if err != nil {
        return "", err
    }

    if locked {
        defer cg.rdb.Del(ctx, lockKey)
        // 查 DB
        val := queryFromDB(key)
        // 回写缓存,加随机过期时间防雪崩
        ttl := 30*time.Minute + time.Duration(randInt(0, 300))*time.Second
        cg.rdb.Set(ctx, key, val, ttl)
        return val, nil
    }

    // 没拿到锁,等一下重试
    time.Sleep(50 * time.Millisecond)
    return cg.Get(ctx, key)
}

里面有几个细节:

  • 布隆过滤器用 bits-and-blooms/bloom 库,启动时把 DB 里已有的 Key 全灌进去
  • 互斥锁用 SETNX 实现,加 3 秒超时防止拿锁的请求挂了导致死锁
  • 回写缓存时 TTL 加了随机偏移,避免大量 Key 在同一时刻过期引发雪崩
  • 没拿到锁的请求 sleep 50ms 后重试,这时缓存大概率已经回写好了

四、布隆过滤器怎么维护

布隆过滤器有个特点:能加 Key 不能删 Key。如果删除了某个 Key,布隆过滤器里还留着标记,会导致误判。

两种解法:

  1. 定期重建:每小时用 DB 全量 Key 重建一次布隆过滤器,简单粗暴,大部分场景够用
  2. Counting Bloom Filter:把每个 bit 换成计数器,支持删除,但内存翻几倍,数据量大不划算

五、注意监控

上线后盯几个指标:

  • Redis 的 rejected_connections:如果互斥锁等待太多,说明热点 Key 频繁失效,考虑加永不过期 + 异步刷新策略
  • DB 的 threads_running:正常应该是个位数,突然飙升说明防线没兜住
  • 布隆过滤器的误判率:理论上 1% 左右,实际跑起来基本感知不到
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。