cachepolicy 本身不提供缓存存储,仅作为策略壳子,必须配合 memorycache 或 redis 等实际缓存后端使用;高并发下需确保线程安全、键生成稳定、异步调用正确、异常响应可控。

CachePolicy 必须配合内存或分布式缓存实例使用
Polly 的 CachePolicy 本身不提供缓存存储,它只是策略壳子,真正存取逻辑由你传入的 ICacheProvider(如 MemoryCacheProvider 或自定义的 Redis 实现)承担。高并发下直接用 MemoryCache 是可行的,但要注意:.NET 6+ 的 MemoryCache 线程安全,而旧版需确保传入的是线程安全封装。
常见错误是只注册了 CachePolicy 却没配缓存后端,结果每次请求都穿透——日志里看不到缓存命中,OnCacheGet/OnCachePut 也不触发。
- 必须显式构造
MemoryCache实例并注入到MemoryCacheProvider - 避免在每次策略创建时 new 一个新
MemoryCache,否则缓存完全不共享 - 若用 Redis,需自行实现
ICacheProvider,且注意序列化/反序列化性能和 null 值处理
缓存键生成必须稳定且区分请求上下文
默认 CacheKeyGenerator 只对委托参数做简单哈希,对 HTTP 请求极易冲突——比如 GET /api/users?id=123 和 GET /api/users?Id=123(大小写不同)可能被当成同一键;又或者未把用户 Token、Accept-Language 等上下文纳入键中,导致缓存污染。
实操建议用显式键生成器:
var cachePolicy = Policy.CacheAsync<HttpResponseMessage>(
new MemoryCacheProvider(memoryCache),
new CacheKeyGenerator((context, token) =>
{
var request = context.OperationKey as HttpRequestMessage;
if (request == null) return Guid.NewGuid().ToString();
// 确保 URL 参数顺序、大小写归一化
var uri = new UriBuilder(request.RequestUri) { Query = SortAndNormalizeQuery(request.RequestUri.Query) };
return $"http-{uri.ToString()}-{request.Headers.Authorization?.Parameter ?? "anon"}";
})
);
-
OperationKey是你调用ExecuteAsync时传入的上下文对象,务必在发起请求前把它设为HttpRequestMessage - 避免在键中拼接原始 JSON 或未清洗的 header 值,防止控制字符或空格引发哈希不一致
- 高并发下键生成函数不能有锁或 I/O,否则成瓶颈
异步缓存策略必须用 Policy.CacheAsync 而非 Policy.Cache
Policy.Cache 是同步策略,强行包装 HttpClient.SendAsync 这类异步方法会阻塞线程池线程,高并发时迅速耗尽线程,表现为你看到大量请求延迟飙升甚至超时,但 CPU 不高——这是典型的同步包装异步的反模式。
正确做法只有这一种:
var cachePolicy = Policy.CacheAsync<HttpResponseMessage>(cacheProvider, ttlSeconds: 60);
- 返回类型必须与被包装方法一致(这里是
Task<httpresponsemessage></httpresponsemessage>),否则编译失败或运行时报InvalidCastException - 不要试图用
.Result或.GetAwaiter().GetResult()强转,这在 ASP.NET Core 请求上下文中大概率引发死锁 - 若业务逻辑需要 fallback 到本地计算,需用
Policy.WrapAsync组合CacheAsync+CircuitBreakerAsync+RetryAsync,顺序不能颠倒
缓存失效和并发写入需靠 TTL 和原子操作兜底
CachePolicy 不支持主动清除(no RemoveAsync),所以别指望能按用户 ID 批量删缓存。高并发场景下多个线程同时发现缓存过期,会一起回源,造成“缓存雪崩”。
缓解方式有限但有效:
- 设置合理
ttlSeconds(如 30–120 秒),避免全站缓存同时过期 - 启用
CacheEntryOptions.SlidingExpiration(通过自定义ICacheProvider透传),让热点数据自动续期 - 对关键接口,在缓存策略外加一层“逻辑锁”:用
ConcurrentDictionary<string task>></string>持有正在加载的任务,避免重复回源(Polly 不管这事,得自己写) - Redis 后端时,利用其
SET key value EX 60 NX原子语义做首次写入保护,但 Polly 默认不集成该逻辑
最易被忽略的一点:CachePolicy 对异常响应(如 500、404)默认也缓存,且不校验状态码。如果上游服务临时出错,错误响应会被多个请求共享。必须用 Policy.CacheAsync 的重载,传入 shouldRetryException 和 shouldCacheResult 委托来精确控制。










