🔥 缓存穿透
问题描述:
-
数据在缓存和数据库中都不存在,导致所有请求都打到数据库,可能引发数据库崩溃。
-
黑客可以利用不存在的 ID 或数据进行攻击,造成缓存穿透。
当系统中引入redis缓存后,一个请求进来后,会先从redis缓存中查询,缓存有就直接返回,缓存中没有就去db中查询,db中如果有就会将其丢到缓存中,但是有些key对应更多数据在db中并不存在,每次针对此次key的请求从缓存中取不到,请求都会压到db,从而可能压垮db。
比如用一个不存在的用户id获取用户信息,不论缓存还是数据库都没有,若黑客利用大量此类攻击可能压垮数据库。
优化解决方案:
-
✅ 布隆过滤器
-
将可能存在的数据哈希到布隆过滤器中,查询前先判断是否存在。
-
能够拦截一定不存在的数据,减少数据库压力。
-
Go 示例:
bf := bloom.NewWithEstimates(1000000, 0.01) // 100万数据,1%误判率 bf.Add([]byte("user:1001")) if !bf.Test([]byte("user:9999")) { fmt.Println("数据一定不存在,拦截请求") }
-
-
✅ 缓存空值
-
将查询结果为空的数据缓存,设置短期过期时间。
if dbVal == "" { redisClient.Set(ctx, "user:10000", "", time.Minute*5) }
-
-
✅ 白名单机制
-
使用 Redis 的
bitmaps
维护可访问 ID 白名单。 -
只允许白名单中的 ID 查询数据库。
redisClient.SetBit(ctx, "whitelist", 1001, 1) // 将 ID 1001 加入白名单 exist := redisClient.GetBit(ctx, "whitelist", 1001).Val() if exist == 0 { fmt.Println("禁止访问") }
-
-
✅ 进行实时监控
-
当发现redis的命中率开始急速降低,需要排查访问对象和访问的数据,和运维人员配合,可以设置黑名单限制对其提供服务(比如:IP黑名单)
-
⚡️ 缓存击穿
问题描述:
-
热点数据在 Redis 中过期,大量请求瞬间打向数据库,可能导致数据库崩溃。
redis中某个热点key(访问量很高的key)过期,此时大量请求同时过来,发现缓存中没有命中,这些请求都打到db上了,导致db压力瞬时大增,可能会打垮db,这种情况成为缓存击穿。
优化解决方案:
-
✅ 互斥锁
-
Redis 中数据过期时,使用分布式锁防止大量请求直接打向数据库。
-
只有一个线程查询数据库,其他线程等待。
lockKey := "lock:hot:data" if redisClient.SetNX(ctx, lockKey, "1", time.Second*5).Val() { // 持有锁,查询数据库并回写缓存 val := queryFromDB(123) redisClient.Set(ctx, "hot:data:123", val, time.Hour) redisClient.Del(ctx, lockKey) } else { // 等待其他线程释放锁 time.Sleep(100 * time.Millisecond) }
-
-
✅ 逻辑过期 + 异步刷新
-
缓存失效后仍返回旧数据,后台异步更新。
-
避免缓存失效期间数据库被打垮。
-
-
✅ 缓存预热
-
在流量高峰前,提前将热点数据加载到 Redis 中。
-
实时监控热点数据,动态调整过期时间。
-
🌪️ 缓存雪崩
问题描述:
-
大量数据在同一时间过期,所有请求打到数据库。
-
导致数据库压力激增,甚至崩溃。
key对应的数据存在,但是极短时间内有大量的key集中过期,此时若有大量的并发请求过来,发现缓存没有数据,大量的请求就会落到db上去加载数据,会将db击垮,导致服务奔溃。
优化解决方案:
-
✅ 过期时间分散
-
在缓存过期时间上增加随机值,防止大量数据同时失效。
ttl := time.Hour + time.Duration(rand.Intn(600))*time.Second // 额外随机 10 分钟 redisClient.Set(ctx, "key", "value", ttl)
-
-
✅ 限流与熔断
-
使用令牌桶或漏桶算法限流。
-
Redis 缓存雪崩时,限制对数据库的访问频率。
limiter := rate.NewLimiter(5, 10) // 每秒 5 个请求,桶容量 10 if !limiter.Allow() { fmt.Println("请求过多,请稍后再试") return }
-
-
✅ 多级缓存
-
构建本地缓存 + Redis 缓存 + 数据库架构。
-
Redis 缓存失效时,短时间内优先读取本地缓存,减少对数据库的冲击。
-
-
✅ 使用锁或队列
-
用加锁或者队列的方式来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上,不适用高并发情况。
-
✅ 3. 总结
问题 | 原因 | 优化解决方案 |
---|---|---|
穿透 | 缓存和数据库都没有数据 | 布隆过滤器、缓存空值、白名单拦截、实时监控 |
击穿 | 热点数据过期,大量请求打DB | 分布式锁、逻辑过期+异步刷新、缓存预热 |
雪崩 | 大量缓存同时过期 | 过期时间分散、限流与削峰、多级缓存架构、使用锁或队列 |