news 2026/4/25 22:24:23

​.NET 实战:Redis 缓存穿透、击穿与雪崩的原理剖析与解决方案

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
​.NET 实战:Redis 缓存穿透、击穿与雪崩的原理剖析与解决方案

在 .NET 高并发系统中,Redis 作为核心缓存层,一旦出现“穿透、击穿、雪崩”,数据库将瞬间承受巨大压力,严重时甚至会导致整个服务雪崩。本文将深入剖析三者原理,并给出可直接落地的 .NET 解决方案。


一、缓存穿透

1. 原理

客户端请求的数据,缓存里没有,数据库里也没有。每次请求都会绕过缓存,直达数据库。恶意攻击者可通过构造大量不存在的 key,使数据库压力剧增。

请求 --> 缓存(Miss) --> 数据库(无记录) --> 返回空 (下次同样请求仍会重复上述路径)

2. 解决方案

A. 缓存空值

查询数据库发现数据不存在时,也在 Redis 中缓存一个短过期时间的空对象(或特殊标记)。下次相同请求直接命中空缓存,不再访问数据库。

.NET 实现示例(StackExchange.Redis):

publicasyncTask<Product?>GetProductAsync(intid){vardb=_redis.GetDatabase();stringkey=$"product:{id}";varcached=awaitdb.StringGetAsync(key);if(cached.HasValue){// 若为特殊空标记,直接返回nullif(cached=="NULL")returnnull;returnJsonSerializer.Deserialize<Product>(cached!);}// 查询数据库varproduct=await_dbContext.Products.FindAsync(id);if(product==null){// 缓存空值,过期时间短,防止占用内存awaitdb.StringSetAsync(key,"NULL",TimeSpan.FromMinutes(1));returnnull;}// 正常缓存awaitdb.StringSetAsync(key,JsonSerializer.Serialize(product),TimeSpan.FromMinutes(10));returnproduct;}
B. 布隆过滤器

在缓存之前加一层布隆过滤器,它用极小的内存判断一个 key一定不存在可能存在。所有合法的 key(如所有商品 ID)提前加载到布隆过滤器,请求先通过过滤器,若判断不存在则直接拒绝,不访问缓存和数据库。

.NET 使用 BloomFilter 示例(借助StackExchange.Redis的布隆模块或本地内存过滤器):

// 使用内存布隆过滤器(适合单机或预热到Redis)varfilter=newBloomFilter(1000000,0.01);// 预计100万数据,1%误判率// 初始化:加载所有合法keyforeach(varidinallProductIds)filter.Add(id);publicasyncTask<Product?>GetWithBloomAsync(intid){if(!filter.Contains(id))returnnull;// 直接返回,不查库// 正常走缓存+数据库逻辑returnawaitGetProductAsync(id);}

布隆过滤器可基于 Redis 的BF.ADD/BF.EXISTS命令(需安装 RedisBloom 模块),或使用BitMap自行实现。


二、缓存击穿

1. 原理

某个热点 key 在过期的一瞬间,大量并发请求同时穿透缓存,直接打到数据库。例如秒杀商品的缓存刚过期,瞬间涌入上万请求重建缓存,数据库极易被压垮。

时间线: T1: 热点key过期 T2: 大量请求同时发现缓存Miss -> 全部查询数据库

2. 解决方案

核心思想:避免让大量线程同时执行数据库查询与缓存重建

A. 互斥锁(SetNX)

第一个线程获取分布式锁,负责查库并重建缓存;其他线程等待锁释放后,直接从缓存读取。

publicasyncTask<Product?>GetProductWithLockAsync(intid){vardb=_redis.GetDatabase();stringkey=$"product:{id}";stringlockKey=$"lock:product:{id}";vartoken=Guid.NewGuid().ToString();varcached=awaitdb.StringGetAsync(key);if(cached.HasValue)returncached=="NULL"?null:JsonSerializer.Deserialize<Product>(cached!);// 尝试获取分布式锁if(awaitdb.LockTakeAsync(lockKey,token,TimeSpan.FromSeconds(10))){try{// 双重检查,可能其他线程已经重建cached=awaitdb.StringGetAsync(key);if(cached.HasValue)returncached=="NULL"?null:JsonSerializer.Deserialize<Product>(cached!);varproduct=await_dbContext.Products.FindAsync(id);if(product==null){awaitdb.StringSetAsync(key,"NULL",TimeSpan.FromMinutes(1));returnnull;}awaitdb.StringSetAsync(key,JsonSerializer.Serialize(product),TimeSpan.FromMinutes(10));returnproduct;}finally{awaitdb.LockReleaseAsync(lockKey,token);}}else{// 未获取到锁,等待后重试awaitTask.Delay(50);returnawaitGetProductWithLockAsync(id);// 递归重试(可限制次数)}}
B. 逻辑过期(永不过期 + 异步重建)

缓存本身不设过期时间,在 value 中额外存储一个逻辑过期时间。读取时若发现逻辑过期,先返回旧值,然后开一个后台任务去更新缓存,避免阻塞请求。

publicclassProductCacheData{publicProductData{get;set;}publicDateTimeExpireTime{get;set;}}publicasyncTask<Product?>GetProductLogicalExpireAsync(intid){vardb=_redis.GetDatabase();stringkey=$"product:{id}";varjson=awaitdb.StringGetAsync(key);if(json.IsNullOrEmpty)returnnull;varcacheData=JsonSerializer.Deserialize<ProductCacheData>(json!);// 逻辑未过期,直接返回if(cacheData.ExpireTime>DateTime.Now)returncacheData.Data;// 逻辑过期,尝试获取锁stringlockKey=$"lock:product:{id}";vartoken=Guid.NewGuid().ToString();if(awaitdb.LockTakeAsync(lockKey,token,TimeSpan.FromSeconds(10))){try{// 异步重建缓存,不阻塞当前请求_=Task.Run(async()=>{varproduct=await_dbContext.Products.FindAsync(id);varnewData=newProductCacheData{Data=product,ExpireTime=DateTime.Now.AddMinutes(10)};awaitdb.StringSetAsync(key,JsonSerializer.Serialize(newData));});}finally{awaitdb.LockReleaseAsync(lockKey,token);}}// 无论是否获得锁,都返回旧数据(可能略微过时,但保证可用)returncacheData.Data;}

三、缓存雪崩

1. 原理

大量缓存在同一时刻失效,或者 Redis 节点宕机,导致巨量请求直接冲向数据库,就像雪崩一样瞬间冲垮系统。

场景:

  • 批量 key 设置了相同的过期时间,在某个时间点共同过期。
  • Redis 集群大面积故障,缓存服务完全不可用。

2. 解决方案

A. 过期时间加随机因子

在基础过期时间上叠加一个随机值,避免大量 key 同时过期。

varbaseExpire=TimeSpan.FromMinutes(10);varrandom=newRandom();varactualExpire=baseExpire+TimeSpan.FromSeconds(random.Next(0,300));// 加0~5分钟awaitdb.StringSetAsync(key,value,actualExpire);
B. 多级缓存 + 限流降级

设置本地内存缓存(如IMemoryCache)作为一级缓冲,Redis 为二级缓存。即使 Redis 故障,本地缓存仍能挡掉部分请求。同时接入熔断降级框架(如 Polly),当数据库压力过大时,直接返回降级数据或限流。

// Polly + 本地缓存降级示例varfallbackPolicy=Policy<Product?>.Handle<Exception>().FallbackAsync(asyncct=>{// 尝试从本地内存缓存获取if(_memoryCache.TryGetValue(key,outProductlocalProduct))returnlocalProduct;returnnull;// 或返回兜底数据});varproduct=awaitfallbackPolicy.ExecuteAsync(async()=>{returnawaitGetFromRedisOrDbAsync(id);});
C. Redis 高可用架构
  • 使用 Redis 哨兵 / 集群模式。
  • 主从 + 自动故障转移,避免单点故障。
  • 对于关键数据,采用持久化(RDB/AOF)确保快速恢复。

四、总结与最佳实践

问题典型特征.NET 核心解决手段
穿透请求不存在的数据缓存空值、布隆过滤器
击穿热点 key 瞬间过期互斥锁(SetNX)、逻辑过期异步重建
雪崩大量 key 同时过期或 Redis 宕机随机过期时间、多级缓存、熔断降级、集群高可用

.NET 实战组合建议:

  1. 所有缓存写入统一封装,自动添加随机过期时间。
  2. 对已知的合法 ID 集合构建布隆过滤器,入口处拦截非法请求。
  3. 热点数据采用“逻辑过期 + 互斥锁”方案,保证高可用。
  4. 结合Polly实现熔断、降级、重试策略,形成纵深防御。
  5. 监控 Redis 和数据库的 QPS、延迟,设置告警阈值。

通过上述方案,.NET 应用能够在面对 Redis 三大缓存经典问题时,保持系统稳定与高可用,有效保护后端数据库。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/25 22:23:25

3PEAK思瑞浦 TP2582-SR SOIC-8 运算放大器

特性 供电电压:3V至36V 差分输入电压范围至电源轨输入轨至-Vs&#xff0c;轨到轨输出过载恢复时间 快速响应:10MHz带宽&#xff0c;8V/us斜率&#xff0c;100ns 低失调电压:在25C时最大3mV&#xff0c;在-40C至85C范围内最大值为3.5mV 在-40C至125C范围内最大值为4mV 极低总谐波…

作者头像 李华
网站建设 2026/4/25 22:23:24

IDEA 2026.1 配置属性识别问题解决

IDEA 2026.1 升级后 Cannot resolve configuration property asc.wx.baseUrl&#xff0c;90% 是注解处理器关闭、配置元数据没生成、dev 多环境未关联、Spring 索引异常&#xff0c;按下面步骤一键修复&#xff1a;一、优先开启注解处理器&#xff08;2026.1 默认常关闭&#x…

作者头像 李华
网站建设 2026/4/25 22:19:26

基于安卓的社区儿童托管预约平台毕业设计

博主介绍&#xff1a;✌ 专注于Java,python,✌关注✌私信我✌具体的问题&#xff0c;我会尽力帮助你。一、研究目的本研究旨在设计并实现一款基于安卓平台的社区儿童托管预约系统以解决当前城市社区中儿童托管服务供需失衡与管理效率低下等问题。随着我国城市化进程加速及双职工…

作者头像 李华
网站建设 2026/4/25 22:16:19

暗黑破坏神2存档编辑革命:告别繁琐,拥抱网页端自由定制

暗黑破坏神2存档编辑革命&#xff1a;告别繁琐&#xff0c;拥抱网页端自由定制 【免费下载链接】d2s-editor 项目地址: https://gitcode.com/gh_mirrors/d2/d2s-editor 你是否曾经为了一个完美的暗黑2角色&#xff0c;反复刷图数小时却一无所获&#xff1f;你是否曾经因…

作者头像 李华
网站建设 2026/4/25 22:13:34

LibreDWG:开源CAD格式解析引擎的技术解析与应用指南

LibreDWG&#xff1a;开源CAD格式解析引擎的技术解析与应用指南 【免费下载链接】libredwg Official mirror of libredwg. With CI hooks and nightly releases. PRs ok 项目地址: https://gitcode.com/gh_mirrors/li/libredwg LibreDWG是一个基于GPLv3许可证的开源DWG文…

作者头像 李华