C# Polly缓存策略方法 C#如何结合Polly和IMemoryCache

3次阅读

polly v8移除了memorycacheprovider,需自行实现icacheprovider或改用policy.wrap组合手动缓存。推荐用imemorycache+retrypolicy,避免cachepolicy复杂性。

C# Polly缓存策略方法 C#如何结合Polly和IMemoryCache

为什么直接用 Polly.Caching.MemoryCacheProvider 会报错?

因为从 Polly v8 开始,MemoryCacheProvider 已被移除,官方不再内置对 IMemoryCache封装。你如果照着旧文档写 new MemoryCacheProvider(new MemoryCache(new MemoryCacheOptions())),编译直接失败——类型不存在。

根本原因是 Polly v8 抽离了缓存抽象,只保留 ICacheProvider<tkey tvalue></tkey> 接口,要求你自行实现或选用社区适配器。

  • 别再找 microsoft.Extensions.http.Polly 里的缓存类,它不包含缓存逻辑
  • IMemoryCache 本身线程安全,但 Polly 的缓存策略(如 CachePolicy)需要你把 key 转换、序列化、过期策略对齐都手动处理
  • 最简路径是:用 Polly.Registry.PolicyRegistry 管理策略 + 自己桥接 IMemoryCache

如何用 CachePolicy 包裹 IMemoryCache 手动缓存?

核心思路不是让 Polly “接管” 缓存,而是用 Polly 的 CachePolicy 做决策,实际读写仍走 IMemoryCache。你需要自己定义 key 生成规则和 value 封装格式。

示例关键片段:

var cache = new MemoryCache(new MemoryCacheOptions()); var cachePolicy = Policy.Cache<String>(     new MemoryCacheProvider(cache),     ttlSeconds: 60,     onCacheGet: (key, ctx) => {         var cached = cache.Get<CacheEntry>(key);         return cached?.Value;     },     onCachePut: (key, value, ctx, span) => {         cache.Set(key, new CacheEntry { Value = value }, TimeSpan.FromSeconds(span.TotalSeconds));     } );

但注意:上面代码中的 MemoryCacheProvider 是假想的——v8 没这个类。所以必须重写为:

  • 自己实现 ICacheProvider<string Object></string>,内部调用 IMemoryCache
  • key 建议用 ctx.OperationKey ?? Guid.NewGuid().ToString(),避免策略复用时冲突
  • value 必须可序列化(IMemoryCache 不限制类型,但 Polly 缓存策略默认 expect object泛型 T)
  • 不要在 onCachePut 里传 TimeSpan.Zero,否则缓存立即失效

推荐做法:绕过 CachePolicy,用 Policy.Wrap 组合 RetryPolicy + 手动缓存逻辑

多数真实场景并不需要 Polly 的缓存语义(比如自动 key 管理、多级缓存钩子),只需要“先查缓存,没命中再执行带重试的业务逻辑”。这时硬套 CachePolicy 反而增加复杂度。

更可控的做法是:

  • 把缓存读写完全收口到一个方法里,例如 TryGetFromCacheAsync<t>(string key, Func<cancellationtoken task>> fallback)</cancellationtoken></t>
  • Policy.WrapAsync 包一层 RetryPolicyCircuitBreakerPolicy,只作用于 fallback 函数
  • IMemoryCache 的 key 命名建议含参数哈希(如 nameof(GetUser) + userId.GetHashCode()),避免拼接字符串出错
  • 注意 IMemoryCache.GetOrCreateAsyncLambda 是 lazy 执行的,里面调用的 fallback 应该已包裹好 Polly 策略

这样既利用了 IMemoryCache 的成熟 API,又没丢掉 Polly 的容错能力,还避开了 v8 缓存抽象的坑。

容易被忽略的生命周期和线程安全细节

IMemoryCache 是 singleton,但 CachePolicy 实例不是线程安全的——不能跨请求共享同一个 CachePolicy 实例,尤其当它内部持有非线程安全的缓存 provider 时。

  • ASP.NET Core 中,始终通过 DI 获取 IMemoryCache,别 new MemoryCache 实例
  • 如果你在 onCacheGet 里反序列化 json 字符串,记得 catch JsonException 并返回 NULL,否则整个缓存策略会抛异常中断流程
  • CachePolicyttlSecondsIMemoryCacheAbsoluteExpirationRelativeToNow 不自动同步,必须手动对齐,否则出现“Polly 认为没过期,但 MemoryCache 已剔除”的情况
  • 调试时留意 context.EarlyValue —— 它表示缓存命中时 Polly 返回的值,不是 IMemoryCache.Get 的原始结果,类型可能被包装过

真正麻烦的从来不是怎么写第一行代码,而是缓存 key 设计是否覆盖所有参数组合、过期时间单位有没有混用秒和毫秒、以及 fallback 函数里异常是否被 Polly 正确捕获而非穿透到上层。

text=ZqhQzanResources