.net 6+ 中应通过 di 注册 imemorycache 而非 new memorycache;需配置 sizelimit 和过期策略;用 getorcreate 防击穿;过期检查惰性触发,绝对时间用 utcnow。

MemoryCache 在 .NET 6+ 里已经不是首选了
如果你用的是 .NET 6 或更新版本(包括 .NET Core 3.1+),MemoryCache 类还在,但官方推荐用 IMemoryCache 接口 + 依赖注入方式注册和使用。直接 new MemoryCache 实例容易漏掉线程安全、过期策略联动、内存压力响应等关键机制。
常见错误现象:MemoryCache 构造时传入 MemoryCacheOptions 却没设 SizeLimit,导致缓存无限增长、GC 压力陡增甚至 OOM;或者多个实例之间不共享清理逻辑,缓存失效不一致。
- 必须通过 DI 容器注册:在
Program.cs里调用services.AddMemoryCache() - 构造函数注入
IMemoryCache,而不是 newMemoryCache - 若真要手动实例化(如单元测试),至少配
SizeLimit和CompactionPercentage
Set 和 Get 时怎么控制过期和大小
IMemoryCache.Set() 的重载很多,但最常用的是带 MemoryCacheEntryOptions 的那个。它决定了缓存项“活多久”“占多大”“被挤出时要不要回调”。
使用场景:查数据库结果缓存 5 分钟,但希望内存紧张时优先淘汰它;或缓存一个大对象(比如导出的 Excel 流),必须限制总大小。
-
SlidingExpiration是滑动过期(每次访问重置计时),AbsoluteExpirationRelativeToNow是固定存活时间,二者不能同时设 - 启用大小限制需两步:注册时设
options.SizeLimit = 1024(单位是“估算字节数”,不是精确值),存入时用SetSize(1)标记该项大小 - 没设
SetSize()的项默认大小为 1,但大对象不标会导致 size 计算严重失真
cache.Set("user:123", user, new MemoryCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromMinutes(5))
.SetSize(512)); // 告诉 cache 这个值大概占 512 单位
缓存击穿和并发读写怎么防
IMemoryCache.GetOrCreate() 是防击穿的核心方法,它把“查 + 存”原子化,避免多个线程同时重建同一缓存项。
容易踩的坑:用 Get() 判断空再 Set(),在高并发下会触发多次重复加载;或者在 GetOrCreate 的 factory 委托里做了耗时 IO 操作(如 DB 查询),却没考虑异常传播和失败缓存。
- 务必用
GetOrCreate(key, entry => { /* 加载逻辑 */ }),别手写 if-else - factory 委托里抛异常,该项不会进缓存,但调用方会收到 exception;如需“缓存失败结果”,得在委托里 catch 后返回兜底值
- 如果加载逻辑本身需要异步(比如 await DB 查询),要用
GetOrCreateAsync(),否则会阻塞线程池
为什么缓存没按时过期或清不掉
最常见的原因是:忘了给 MemoryCacheEntryOptions 设置过期策略,或者设置了但用了错误的时钟类型(比如本地时间 vs UTC);另一个是缓存项被其他代码显式 Remove() 或整个 cache 被 Dispose() 了。
性能影响:过期检查不是定时轮询,而是惰性触发——只有在 Get 或 TryGetValue 时才检查是否过期。所以“看起来没过期”可能只是还没被访问。
- 绝对过期时间一律用
DateTimeOffset.UtcNow计算,别用DateTime.Now,否则跨时区部署会出错 - 调用
cache.Remove(key)是立即生效的,但注意 key 区分大小写 - 如果用了
PostEvictionCallbacks,回调执行是异步的,不能在里面做阻塞操作
复杂点在于:缓存项的过期、大小淘汰、回调触发都由后台线程协作完成,不完全可控。真正要稳,得靠监控 CacheStatistics(需启用)和压测验证行为。











