必须通过DI注入IMemoryCache并调用AddMemoryCache注册,禁用MemoryCache.Default和new实例;GetOrCreate防击穿;大对象需SetSize;Sliding与Absolute过期不可共存;缓存对象须线程安全。

必须用 DI 注入 IMemoryCache,别碰 MemoryCache.Default 或 new
直接用 MemoryCache.Default 或 new MemoryCache(new MemoryCacheOptions()) 是最常见也最危险的错误。前者来自 System.Runtime.Caching,和 ASP.NET Core 的 Microsoft.Extensions.Caching.Memory 完全不兼容;后者创建的是孤立实例,不参与 DI 生命周期,导致:
- 多个 Service 注入的
_cache实际是不同对象,缓存项无法共享 -
SizeLimit配置失效,SetSize()白设,内存悄悄涨满 - 过期检查、紧凑(compaction)、GC 压力响应全部停摆,OOM 前零预警
正确做法只有一条:在 Program.cs 中调用 builder.Services.AddMemoryCache(options => options.SizeLimit = 100 * 1024 * 1024),然后构造函数注入 IMemoryCache。
GetOrCreate 是防击穿的唯一可靠方式
手写 if (!_cache.TryGetValue(key, out var v)) { v = LoadFromDb(); _cache.Set(key, v, ...); } 在高并发下必然触发 N 次重复加载 —— 10 个请求同时判断 key 缺失,就会并行执行 10 次数据库查询。
GetOrCreate() 内部加锁,确保 factory 委托只执行一次。关键点:
- 过期策略必须在 factory 的
entry参数上设置:entry.SlidingExpiration = TimeSpan.FromMinutes(5),外部设置无效 - factory 中抛异常,缓存不会写入,调用方直接收到 exception;如需缓存空结果,得自己
try/catch后返回 - 若加载逻辑含
await(比如 EF Core 查询),必须用GetOrCreateAsync(),否则线程池阻塞
SizeLimit 不是“设了就管用”,必须 SetSize() 配合
只在 DI 注册时设 options.SizeLimit = 100 * 1024 * 1024 远远不够。所有未调用 SetSize() 的缓存项,默认按大小 1 计算 —— 存一个 15MB 的 byte[],缓存系统却只当它占 1 单位。
后果就是内存失控、GC 频繁、服务卡顿。实操建议:
- 大对象(如文件流、序列化后的 DTO)必须显式标大小:
entry.SetSize(15 * 1024 * 1024) - 小对象可统一标
SetSize(1),避免混用导致 size 统计失真 - 不要依赖“估算”:SizeLimit 单位是字节数(近似值),不是条目数
Sliding 和 Absolute 过期不能共存,否则运行时报错
代码里同时设置 entry.SlidingExpiration 和 entry.AbsoluteExpirationRelativeToNow 会直接抛出 ArgumentException: Both sliding and absolute expiration cannot be set。
这不是警告,是运行时崩溃。实际选型逻辑很简单:
- 用户会话类数据(如 token、临时配置)适合滑动过期:
entry.SlidingExpiration = TimeSpan.FromMinutes(30) - 定时刷新类数据(如天气预报、汇率)适合绝对过期:
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1) - 二者混用场景极少,真有需要,优先用 Absolute + 定时任务主动刷新
缓存对象本身还得是线程安全或不可变的 —— 存一个 List<T> 进去,外面直接 Add(),所有读取方看到的就是被改过的脏数据。这点容易被忽略,但线上问题往往就出在这儿。


















