ASP.NET Core中如何用Polly根据响应体内容实现自定义重试策略?
构造适配统一返回体的Polly重试策略实现
核心实现思路:由于对接的外部API所有请求均返回200状态码,因此需要自定义策略触发条件,直接解析响应内容判断是否需要重试,同时要处理响应流可重复读取的问题,避免影响后续Refit的反序列化逻辑。
第一步:定义全局可重试错误配置(适配多API通用场景)
可预先定义静态类统一维护可重试的错误标识,比如限流、服务暂时不可用这类允许重试的错误Code,后续多API对接时可直接复用:
public static class RetryableErrorCodes { // 可根据对接API的实际错误码规则补充 public static readonly HashSet<string> Codes = new() { "RATE_LIMIT_EXCEEDED", // 限流 "SERVICE_BUSY", // 服务繁忙 "GATEWAY_TIMEOUT" // 网关超时 }; }
第二步:构造重试策略
直接构造IAsyncPolicy<HttpResponseMessage>类型的策略,适配AddPolicyHandler的入参要求:
var retryPolicy = Policy .HandleResult<HttpResponseMessage>(async response => { // 先把响应内容加载到缓冲区,允许多次读取,避免后续Refit反序列化失败 await response.Content.LoadIntoBufferAsync(); // 读取响应内容反序列化为统一返回体 var result = await response.Content.ReadFromJsonAsync<ApiResultResponse>(); if (result == null) return false; // 触发重试的条件:Success为false 且 错误列表中存在可重试的错误 return !result.Success && result.Errors != null && result.Errors.Any(e => RetryableErrorCodes.Codes.Contains(e.Code)); }) .WaitAndRetryAsync( retryCount: 3, // 最大重试次数可按需调整 sleepDurationProvider: (retryAttempt, _) => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), // 指数退避间隔:1s、2s、4s onRetryAsync: async (result, timeSpan, retryAttempt, _) => { // 重试前的统一日志逻辑,可按需注入ILogger实现 var apiResult = await result.Result.Content.ReadFromJsonAsync<ApiResultResponse>(); Console.WriteLine($"请求{apiResult?.RequestId}失败,第{retryAttempt}次重试,错误信息:{string.Join(",", apiResult?.Errors?.Select(e=>e.Message) ?? Array.Empty<string>())}"); });
若所有
Success=false的情况都需要重试,不需要区分错误码,可直接删除错误码判断逻辑,返回!result.Success即可。
第三步:注册到Refit客户端
和原有注册逻辑完全兼容,直接传入构造好的策略即可:
services.AddRefitClient<ISomeApi>(refitSettings) .ConfigureHttpClient(c => { c.BaseAddress = someApiBaseUrl; }) .AddPolicyHandler(retryPolicy);
多API通用封装方案
如果有多个对接的外部API都使用这套统一返回体,可把策略构造逻辑封装为扩展方法,避免重复代码:
public static class PollyPolicyExtensions { public static IAsyncPolicy<HttpResponseMessage> CreateApiRetryPolicy(int retryCount = 3) { return Policy .HandleResult<HttpResponseMessage>(async response => { await response.Content.LoadIntoBufferAsync(); var result = await response.Content.ReadFromJsonAsync<ApiResultResponse>(); return result != null && !result.Success && result.Errors != null && result.Errors.Any(e => RetryableErrorCodes.Codes.Contains(e.Code)); }) .WaitAndRetryAsync( retryCount: retryCount, sleepDurationProvider: (attempt, _) => TimeSpan.FromSeconds(Math.Pow(2, attempt)) ); } }
注册时直接调用扩展方法即可,支持自定义重试次数:
// 对接API1 services.AddRefitClient<ISomeApi>(refitSettings) .ConfigureHttpClient(c => c.BaseAddress = someApiBaseUrl) .AddPolicyHandler(PollyPolicyExtensions.CreateApiRetryPolicy()); // 对接API2,复用同一套策略 services.AddRefitClient<IOtherApi>(refitSettings) .ConfigureHttpClient(c => c.BaseAddress = otherApiBaseUrl) .AddPolicyHandler(PollyPolicyExtensions.CreateApiRetryPolicy(5));
注意事项
- 确保
ApiResultResponse和Error类的序列化属性和外部API返回的字段名完全匹配,避免反序列化失败 - 仅对GET、PUT等幂等请求加重试策略,如果是POST这类非幂等接口,要确认重复提交不会造成业务异常,避免重复下单、重复扣款等问题
- 若响应体体积很大,
LoadIntoBufferAsync会占用较多内存,可根据业务场景调整为流式读取指定字段后重置流位置
内容的提问来源于stack exchange,提问作者xceled
相关产品推荐
相关产品推荐

