ASP.NET高流量站点Gen2 GC过高问题排查及缓存项优化最佳实践
问题根因确认
你观察到的高Gen2 GC问题确实和当前的缓存实现直接相关,原因如下:
- 你设置的缓存过期时间为10分钟,远长于Gen0、Gen1的默认回收周期(通常Gen0回收间隔仅数秒到数十秒),缓存对象会在多次新生代回收后快速晋升到Gen2堆
HttpRuntime.Cache的过期清理机制为被动触发:只有访问过期key、或后台低频率的全局缓存扫描(默认间隔数分钟)时才会清理过期对象,过期的缓存对象会长期滞留在Gen2堆中,只能等到耗时最长的全堆Gen2 GC时才会被回收- 你提到的「Gen1中无存活对象」也完全符合这个特征:短生命周期的请求级对象都在Gen0就被回收,只有长存活的缓存对象会晋升到Gen2,Gen1自然没有持续存活的对象
现有代码的优化方案
1. 修复类型判断BUG
现有代码中的value.GetType() != typeof(T)存在逻辑漏洞:如果T是接口/抽象类、或可空值类型,实际存储的实现类/装箱后类型会导致判断误判,触发不必要的缓存重建,额外生成大量垃圾对象。修改方案如下:
// 替换原有类型判断逻辑 if (value == null || !(value is T typedValue)) { // 缓存重建逻辑 // ... return data != null ? data.Data : default; } return typedValue;
2. 优化缓存实现
- 替换
HttpRuntime.Cache为System.Runtime.Caching.MemoryCache(.NET Framework 4.0+均支持,.NET Core/.NET 5+更推荐内置的Microsoft.Extensions.Caching.Memory.MemoryCache),新的缓存实现支持更灵活的清理策略、内存阈值触发回收、过期回调等能力,可大幅降低过期对象滞留的概率 - 缓存时间计算使用
DateTime.UtcNow替代DateTime.Now,避免服务器时区调整、时间同步等操作导致的缓存过期时间异常
3. 增加缓存击穿防护
高流量场景下如果缓存过期瞬间有大量请求命中同一个key,会同时触发getData()调用,瞬间生成大量重复的对象,这些对象大多还没插入缓存就成为垃圾,大幅加重GC负担。可添加按key的双检锁或限流逻辑:
// 可配合ConcurrentDictionary<key, SemaphoreSlim>实现同key单线程重建缓存 private static readonly ConcurrentDictionary<string, SemaphoreSlim> _keyLocks = new(); // 缓存重建前先获取对应key的锁,避免重复重建
4. 主动清理过期缓存
针对你10分钟更新一次赛事数据的业务特性,可以单独维护所有赛事相关的缓存key列表,每9分钟主动批量删除一次过期缓存,主动移除的对象如果还未晋升到Gen2,会直接在新生代GC中被回收,避免大量对象堆积到Gen2。
内容的提问来源于stack exchange,提问作者Mike Flynn
相关产品推荐
相关产品推荐

