Redis 缓存实践:穿透、击穿、雪崩在 .NET 项目中的应对
Words: 1010Read Time: 3 minLast edited: 2026-8-4
博客的文章详情接口,上线初期直接查库,随着文章变多、爬虫时不时扫一把,数据库压力肉眼可见。于是引入 Redis 做缓存,顺带把缓存的三大经典问题——穿透、击穿、雪崩——从面试八股变成了实战。这篇记录我的应对方案,基于 StackExchange.Redis。
三个概念先捋清楚
- 缓存穿透:查一个根本不存在的数据,缓存和数据库都 miss,请求每次都打到库。恶意刷不存在的 id 就是典型
- 缓存击穿:某个热点 key 恰好过期,大量并发同时 miss,一窝蜂去打数据库
- 缓存雪崩:大量 key 在同一时刻集体过期,数据库瞬间被压垮
一句话区分:穿透是"查不到",击穿是"一个热点倒了",雪崩是"一片全倒了"。
穿透:空值缓存加布隆过滤器
我的做法分两层。第一层最简单:数据库查不到,也把"空结果"写进缓存,过期时间设短一点,比如 2 分钟。这样连续刷同一个不存在的 id,只有第一次会打库。
第二层是布隆过滤器:把存在的文章 id 全部放进去,请求先过过滤器,判断"一定不存在"的直接返回。注意布隆过滤器有极低误判率,但只会误判"存在",放行的请求最终也只是多查一次库,不会返回错误结果。
击穿:互斥锁
热点 key 过期瞬间,不能让所有请求都去回源。思路是抢锁:谁抢到谁查库写缓存,其他人稍等再读缓存。Redis 的
SET key value NX EX 天然就是分布式锁,完整代码如下:锁一定要带过期时间。我第一次写的时候忘了加 EX,结果某次异常导致锁没释放,那个 key 整整卡了十分钟才想起来手动删——这是本场最实在的坑。
雪崩:过期时间加随机
如果缓存是批量灌进去的(比如凌晨预热),大家的 TTL 一样,就会在同一时刻集体失效。解决办法简单到不像个方案:基础 TTL 上加一个随机值,30 分钟变成 30 到 40 分钟,过期时间点自然就散开了。上面代码里的
Random.Shared.Next(0, 10) 干的就是这件事。上手 StackExchange.Redis
.NET 连 Redis 主流就是 StackExchange.Redis,NuGet 装上后:
两个注意点:ConnectionMultiplexer 要做成全局单例,它内部管理连接池,别每次请求都 Connect,那是非常经典的错误用法;它自带自动重连,网络抖动时不用自己写重试。其次是 Redis 里存的都是字符串(或字节),对象要自己序列化,我用 System.Text.Json,简单场景足够了。
上线这套缓存后,详情接口平均耗时从几十毫秒降到个位数,数据库 CPU 也安静了。三大问题听起来唬人,落到代码上其实都是几行的事——关键是想清楚自己的场景会踩中哪一个。
Loading...