Redis缓存击穿、缓存穿透、缓存雪崩(定义、产生原因、解决方案--代码示例)

在这里插入图片描述

前言

Redis 缓存作为高性能的数据访问层,在实际开发中经常面临三大经典问题:缓存击穿、缓存穿透、缓存雪崩。

本文将从它们各自的定义、产生的原因、实际开发过程中的解决方案出发,为大家详细描述相关的信息,并附有相关的go代码示例(嗯…最近go写的比较多,大家也可以用其它语言带入,原理都是一样的)


🧨 一、缓存穿透(Cache Penetration)

❓是什么?

  • 客户端频繁请求数据库中根本不存在的 Key,缓存不命中、数据库也查询不到,导致所有请求都打到数据库,形成“穿透”。

⚠️产生原因:

  • 恶意攻击者大量请求随机 key

  • 参数异常或用户请求错误

✅解决方案

1. 设置空值缓存(推荐)
// 查询用户信息,缓存穿透处理:不存在也缓存
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
}
2. 使用布隆过滤器(高并发下更优)
  • 将可能存在的 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))
}

💥 二、缓存击穿(Cache Breakdown)

❓是什么?

  • 某个热点 key 失效的一瞬间,大量请求同时击穿缓存打到数据库,瞬间压垮 DB。

⚠️产生原因:

  • 热点数据失效,瞬时并发请求全部走数据库

  • 缓存更新不及时

✅解决方案

1. 设置互斥锁(分布式锁)

每次缓存未命中时,只有一个线程能去查数据库,其他等待。

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
}
2. 提前续期:热点 key 永不过期
  • 定时任务刷新 key 的 TTL,保持热 key 存在

  • 或缓存永不过期,仅通过后台异步更新值

3. 多级缓存:本地缓存 + Redis
  • 使用 Go 的 sync.Map 或 bigcache 等组件构建一级缓存

❄️ 三、缓存雪崩(Cache Avalanche)

❓是什么?

  • 大量缓存同时过期或 Redis 故障,导致请求瞬间全部打到数据库,造成系统雪崩。

⚠️产生原因:

  • 统一设置了大量 key 的相同过期时间
  • Redis 整体不可用或宕机

✅解决方案

1. 过期时间加随机
  • 避免所有缓存同一时间失效
ttl := 60 + rand.Intn(30) // 60~90秒
redisClient.Set(ctx, key, val, time.Duration(ttl)*time.Second)
2. 构建本地缓存备份(降级缓存)
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
}
3. 熔断降级 + 限流保护 (golang中gin+redis实现熔断与限流)
  • 高并发或 DB 延迟高时,短路请求或返回默认值,避免连锁反应
✅ 1. 熔断机制(Circuit Breaker)
📌 核心思想:
  • 监控数据库或下游服务的错误率和响应时间

  • 如果连续失败或延迟严重,则“熔断”请求,快速失败,防止压垮系统

  • 一段时间后尝试恢复(Half-Open 状态)

✅ 用处:

断开数据库压力路径,即便缓存失效也避免瞬时打爆 DB

✅ 2. 限流机制(Rate Limiting)
📌 核心思想:
  • 限制每秒(或每分钟)访问次数,控制系统负载

  • 超出请求数的用户请求被拒绝、排队或延迟

✅ 用处:
  • 限制缓存失效后的并发量,避免同时大量穿透到数据库

🔚 总结对比表

问题类型现象原因核心解决方案
穿透缓存&DB都没数据攻击/异常ID空值缓存 / 布隆过滤器
击穿热点 key 同时失效高并发打穿缓存加锁 / 永久缓存 / 本地缓存
雪崩大面积缓存同时失效集中过期 / Redis挂随机过期 / 降级缓存 / 限流熔断
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

卜锦元

白嫖是人类的终极快乐,我也是

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值