Redis 缓存作为高性能的数据访问层,在实际开发中经常面临三大经典问题:缓存击穿、缓存穿透、缓存雪崩。
本文将从它们各自的定义、产生的原因、实际开发过程中的解决方案出发,为大家详细描述相关的信息,并附有相关的go代码示例(嗯…最近go写的比较多,大家也可以用其它语言带入,原理都是一样的)
恶意攻击者大量请求随机 key
参数异常或用户请求错误
// 查询用户信息,缓存穿透处理:不存在也缓存
func GetUserInfo(id string) (string, error) {
key := "user:" + id
val, err := redisClient.Get(ctx, key).Result()
if err == redis.Nil {
// 缓存未命中,查数据库
dbVal := queryDB(id)
if dbVal == "" {
// 防止穿透:缓存空值,设置短过期
redisClient.Set(ctx, key, "null", 30*time.Second)
return "", nil
}
redisClient.Set(ctx, key, dbVal, time.Hour)
return dbVal, nil
}
if val == "null" {
// 命中空值缓存
return "", nil
}
return val, nil
}
将可能存在的 key 预先存入布隆过滤器
只有布隆判断为可能存在的,才访问 Redis / DB
可选库:github.com/bits-and-blooms/bloom/v3
var bf = bloom.NewWithEstimates(1000000, 0.01)
func InitBloomFromDB() {
for _, id := range queryAllIDs() {
bf.Add([]byte(id))
}
}
func CheckWithBloom(id string) bool {
return bf.Test([]byte(id))
}
热点数据失效,瞬时并发请求全部走数据库
缓存更新不及时
每次缓存未命中时,只有一个线程能去查数据库,其他等待。
func GetProductDetail(id string) string {
key := "product:" + id
val, err := redisClient.Get(ctx, key).Result()
if err == redis.Nil {
lockKey := "lock:" + id
ok, _ := redisClient.SetNX(ctx, lockKey, "1", 10*time.Second).Result()
if ok {
defer redisClient.Del(ctx, lockKey)
val = queryDB(id)
redisClient.Set(ctx, key, val, 5*time.Minute)
} else {
// 等待缓存刷新后再取
time.Sleep(100 * time.Millisecond)
return GetProductDetail(id)
}
}
return val
}
定时任务刷新 key 的 TTL,保持热 key 存在
或缓存永不过期,仅通过后台异步更新值
ttl := 60 + rand.Intn(30) // 60~90秒
redisClient.Set(ctx, key, val, time.Duration(ttl)*time.Second)
var localCache = sync.Map{}
func GetWithFallback(id string) string {
key := "data:" + id
val, err := redisClient.Get(ctx, key).Result()
if err != nil {
if v, ok := localCache.Load(key); ok {
return v.(string)
}
val = queryDB(id)
redisClient.Set(ctx, key, val, time.Minute)
localCache.Store(key, val)
}
return val
}
监控数据库或下游服务的错误率和响应时间
如果连续失败或延迟严重,则“熔断”请求,快速失败,防止压垮系统
一段时间后尝试恢复(Half-Open 状态)
断开数据库压力路径,即便缓存失效也避免瞬时打爆 DB
限制每秒(或每分钟)访问次数,控制系统负载
超出请求数的用户请求被拒绝、排队或延迟
问题类型 | 现象 | 原因 | 核心解决方案 |
---|---|---|---|
穿透 | 缓存&DB都没数据 | 攻击/异常ID | 空值缓存 / 布隆过滤器 |
击穿 | 热点 key 同时失效 | 高并发打穿缓存 | 加锁 / 永久缓存 / 本地缓存 |
雪崩 | 大面积缓存同时失效 | 集中过期 / Redis挂 | 随机过期 / 降级缓存 / 限流熔断 |