记一次 .NET 某企业审批系统 崩溃分析

记一次 .NET 某企业审批系统 崩溃分析

背景:深夜的告警电话某个周五晚上11点,我正在家里追剧,突然手机疯狂震动------监控系统发出P0级告警:某企业审批系统响应超时,大量请求失败。作为值班技术博主,我立刻登录服务器查看。这个系统是典型的.NET Framework 4.7.2企业级应用,负责处理企业内部的请假、报销、合同审批等流程。上线两年多一直稳定运行,但最近随着业务增长,并发量从日均5万飙升至20万,系统开始出现偶发性崩溃。## 初步排查:内存泄漏的征兆登录服务器后,我首先使用perfmon监控进程性能,发现w3wp.exe(IIS工作进程)的内存占用从正常的1.2GB飙升至4.5GB,且持续增长不回落。这是典型的内存泄漏迹象。### 查看内存快照使用WinDbg附加到进程,执行!dumpheap -stat命令,发现System.String对象数量异常多,达到300万个,占用了近2GB内存。但更可疑的是,CustomApprovalWorkflow类实例数高达50万个,这远远超过了业务预期。csharp// 模拟业务代码片段:工作流引擎public class CustomApprovalWorkflow{ private List<ApprovalStep> steps = new List<ApprovalStep>(); public void ProcessApproval(ApprovalRequest request) { // 每次处理请求时,都会创建新的工作流实例 var workflow = new CustomApprovalWorkflow(); workflow.LoadSteps(request.WorkflowId); // ... 处理逻辑 // 但这里没有及时释放workflow对象 }}## 深入分析:找到罪魁祸首通过!gcroot命令追踪对象引用链,发现所有CustomApprovalWorkflow对象都被一个静态字典_workflowCache引用着。csharp// 问题代码示例:静态缓存导致内存泄漏public class WorkflowManager{ // 静态字典,用于缓存工作流实例 private static Dictionary<string, CustomApprovalWorkflow> _workflowCache = new Dictionary<string, CustomApprovalWorkflow>(); public static CustomApprovalWorkflow GetWorkflow(string workflowId) { if (!_workflowCache.ContainsKey(workflowId)) { var workflow = LoadWorkflowFromDatabase(workflowId); _workflowCache.Add(workflowId, workflow); } return _workflowCache[workflowId]; } // 问题:没有提供清除缓存的机制! // 随着时间推移,缓存无限增长}## 修复方案:引入过期策略与弱引用找到问题后,我设计了两种修复方案:### 方案一:使用WeakReference弱引用csharp// 修复后的代码:使用弱引用避免内存泄漏public class WorkflowManager{ // 改用WeakReference,允许GC回收不再使用的对象 private static Dictionary<string, WeakReference<CustomApprovalWorkflow>> _workflowCache = new Dictionary<string, WeakReference<CustomApprovalWorkflow>>(); public static CustomApprovalWorkflow GetWorkflow(string workflowId) { if (_workflowCache.TryGetValue(workflowId, out var weakRef)) { if (weakRef.TryGetTarget(out var workflow)) { return workflow; } else { // 对象已被回收,移除过期条目 _workflowCache.Remove(workflowId); } } var newWorkflow = LoadWorkflowFromDatabase(workflowId); _workflowCache[workflowId] = new WeakReference<CustomApprovalWorkflow>(newWorkflow); return newWorkflow; }}### 方案二:使用MemoryCache实现过期策略csharp// 更完善的方案:使用System.Runtime.Caching.MemoryCacheusing System.Runtime.Caching;public class WorkflowManager{ private static MemoryCache _cache = MemoryCache.Default; private static readonly CacheItemPolicy _policy = new CacheItemPolicy { // 设置绝对过期时间,30分钟未使用自动回收 AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(30), // 设置优先级,允许系统在内存压力大时优先回收 Priority = CacheItemPriority.Low }; public static CustomApprovalWorkflow GetWorkflow(string workflowId) { if (_cache.Get(workflowId) is CustomApprovalWorkflow workflow) { return workflow; } var newWorkflow = LoadWorkflowFromDatabase(workflowId); _cache.Set(workflowId, newWorkflow, _policy); return newWorkflow; }}## 性能对比与验证修复后,我在测试环境进行压力测试:| 指标 | 修复前 | 修复后 ||------|--------|--------|| 并发用户数 | 500 | 500 || 内存峰值 | 4.2GB | 1.1GB || 平均响应时间 | 2.3s | 0.4s || GC暂停时间 | 800ms | 50ms || 崩溃次数 | 5次/小时 | 0次 |## 总结这次崩溃的根本原因是一个看似无害的静态缓存设计。在.NET中,静态对象会一直存活到应用程序域卸载,如果缓存不设限,就会导致内存泄漏,最终引发OutOfMemoryException。从这次排查中,我总结出几个经验:1. 静态集合要谨慎使用 :静态Dictionary、List等集合如果没有清理机制,就是内存泄漏的温床2. 合理使用WeakReference :在需要缓存但不想阻止GC回收时,WeakReference是个好选择3. 利用现有缓存框架 :MemoryCache提供了完善的过期策略,比手动管理安全得多4. 监控内存指标 :使用性能计数器监控Gen 2 Collections和Large Object Heap size,能早期发现内存问题最后提醒大家:代码中的"小"缓存,可能是系统崩溃的"大"隐患。在.NET开发中,内存管理是门必修课,尤其在高并发场景下,一个不经意的静态集合,足以让整个系统轰然倒塌。

相关推荐
_Evan_Yao3 小时前
Redis 缓存穿透、击穿、雪崩,一次讲清!
java·spring·mybatis
梓豪csd4 小时前
消息订阅中心:谁该收到哪条通知
java·开发语言·人工智能·低代码·自动化·1024程序员节
秋饼4 小时前
Spring AI 2.1 Message Parts 深度实战:推理签名回放与跨轮记忆持久化的源码级落地
java·ai·技术分享·后端开发
thefool1122665 小时前
TCP 1
java·1024程序员节
金銀銅鐵7 小时前
[Java] 用 GUI 展示 EnumSet 增减元素时的内部变化(下)
java·后端·ai编程
AI人工智能+电脑小能手7 小时前
一次请求打几个下游,这个Java并行任务工具类6个方法按序聚合
java·线程池·并发编程·1024程序员节·批量处理·并行任务
paopaokaka_luck9 小时前
心理咨询管理系统(WebSocket实时沟通、基于用户的协同过滤、 ECharts多维分析、 预约业务流程、预约校验与定时处理)
java·vue.js·spring boot·mysql·spring·echarts·1024程序员节
2301_32241428049 小时前
springboot农产品交易平台的设计与实现08055-计算机毕设 原创(免费领源码+部署教程+开发环境)
java·spring boot·redis·mysql·tomcat·maven·mybatis
考虑考虑10 小时前
JDK26的JEP 500属性
java·后端·java ee
LMT流氓兔10 小时前
第五单元——第十二课:把脚本整理成项目
java·数据库·python·langchain·1024程序员节