Polly重试策略与Ocelot网关断路器结合失效问题求助
问题描述
在Ocelot API网关中尝试通过DelegatingHandler整合Polly的重试策略与断路器功能,目前断路器可正常工作(调用API3次后触发打开),但重试策略完全不生效,调用时直接抛出异常,无任何重试行为。
相关代码片段
"ExceptionsAllowedBeforeBreaking": 3, .CircuitBreakerAsync(route.QosOptions.ExceptionsAllowedBeforeBreaking,
下游API接口代码:
[HttpGet("RaiseException")] public async Task<int> RaiseException() { await Task.Delay(1); throw new Exception("Mock Exception"); }
自定义DelegatingHandler
public class PollyWithInternalServerErrorCircuitBreakingDelegatingHandler : DelegatingHandler { private readonly IOcelotLogger _logger; private readonly Polly.Wrap.AsyncPolicyWrap<HttpResponseMessage> _circuitBreakerPolicies; public PollyWithInternalServerErrorCircuitBreakingDelegatingHandler(DownstreamRoute route, IOcelotLoggerFactory loggerFactory) { _logger = loggerFactory.CreateLogger<PollyWithInternalServerErrorCircuitBreakingDelegatingHandler>(); var pollyQosProvider = new PollyQoSProvider(route, loggerFactory); var retryPolicy = HttpPolicyExtensions.HandleTransientHttpError() .OrResult(r => r.StatusCode == HttpStatusCode.NotFound) .WaitAndRetryAsync(2, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))); var responsePolicy = Policy.HandleResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.InternalServerError) .CircuitBreakerAsync(route.QosOptions.ExceptionsAllowedBeforeBreaking, TimeSpan.FromMilliseconds(route.QosOptions.DurationOfBreak)); _circuitBreakerPolicies = Policy.WrapAsync(pollyQosProvider.CircuitBreaker.Policies) .WrapAsync(retryPolicy).WrapAsync(responsePolicy); } protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { try { return await _circuitBreakerPolicies.ExecuteAsync(() => base.SendAsync(request, cancellationToken)); } catch (BrokenCircuitException ex) { _logger.LogError($"Reached to allowed number of exceptions. Circuit is open", ex); throw; } catch (HttpRequestException ex) { _logger.LogError($"Error in CircuitBreakingDelegatingHandler.SendAsync", ex); throw; } } }
Ocelot构建器扩展
public static class OcelotBuilderExtensions { public static IOcelotBuilder AddPollyWithInternalServerErrorHandling(this IOcelotBuilder builder) { var errorMapping = new Dictionary<Type, Func<Exception, Error>> { {typeof(TaskCanceledException), e => new RequestTimedOutError(e)}, {typeof(TimeoutRejectedException), e => new RequestTimedOutError(e)}, {typeof(BrokenCircuitException), e => new RequestTimedOutError(e)} }; builder.Services.AddSingleton(errorMapping); DelegatingHandler QosDelegatingHandlerDelegate(DownstreamRoute route, IOcelotLoggerFactory logger) { return new PollyWithInternalServerErrorCircuitBreakingDelegatingHandler(route, logger); } builder.Services.AddSingleton((QosDelegatingHandlerDelegate)QosDelegatingHandlerDelegate); return builder; } }
Program.cs配置
var builder = WebApplication.CreateBuilder(args); //Ocelot加载配置文件 builder.Configuration.AddJsonFile($"ocelot.config.{builder.Environment.EnvironmentName}.json", optional: false, reloadOnChange: true); builder.Services.AddOcelot(builder.Configuration) .AddPollyWithInternalServerErrorHandling();
Ocelot路由配置
"UpstreamHttpMethod": [ "GET" ], "QoSOptions": { //触发断路器前允许的异常次数 "ExceptionsAllowedBeforeBreaking": 3, //断路器打开后的持续时间(毫秒) "DurationOfBreak": 5000, //请求超时阈值(毫秒) "TimeoutValue": 100000 }
问题分析与修复方案
核心问题1:重试策略未覆盖当前异常场景
下游API直接抛出Exception,但当前重试策略仅处理:
HandleTransientHttpError():涵盖HTTP 5xx状态码和HttpRequestExceptionOrResult(r => r.StatusCode == HttpStatusCode.NotFound):仅处理404响应
下游抛出的未捕获异常无法被当前重试策略识别,导致重试逻辑不触发。
核心问题2:Policy包装顺序错误
Polly的WrapAsync执行顺序是从外到内,你当前的包装顺序让断路器逻辑先于重试执行,一旦断路器触发,重试策略根本没有执行机会。正确顺序应该是重试在外,断路器在内,确保重试失败后再触发断路器。
修复步骤
1. 扩展重试策略的异常覆盖范围
修改重试策略,捕获下游抛出的异常类型:
var retryPolicy = HttpPolicyExtensions.HandleTransientHttpError() .OrResult(r => r.StatusCode == HttpStatusCode.NotFound) .Or<Exception>() // 可替换为更精确的异常类型,比如HttpRequestException .WaitAndRetryAsync(2, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
2. 修正Policy包装顺序
将重试策略放在最外层,确保重试逻辑优先执行:
// 正确顺序:重试 → 自定义断路器 → Ocelot默认断路器 _circuitBreakerPolicies = Policy.WrapAsync(retryPolicy) .WrapAsync(responsePolicy) .WrapAsync(pollyQosProvider.CircuitBreaker.Policies);
3. 移除重复断路器(可选)
如果不需要Ocelot默认的QoS断路器配置,可以直接移除pollyQosProvider.CircuitBreaker.Policies,仅保留自定义的重试+断路器组合,避免逻辑冲突。
4. 验证异常传递
在SendAsync中添加日志,确认下游返回的响应状态或异常类型,确保重试策略的触发条件匹配实际场景:
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { try { var response = await base.SendAsync(request, cancellationToken); _logger.LogInformation($"下游响应状态码:{response.StatusCode}"); return response; } catch (Exception ex) { _logger.LogError($"下游抛出异常:{ex.Message}"); throw; } }
内容的提问来源于stack exchange,提问作者user584018
相关产品推荐
相关产品推荐

