Polly 重试不同 url

Polly retry with different url

我正在尝试使用 polly 创建一个解决方案,我请求另一个 api。
我有一个指向同一服务的多个实例的 URL 列表。
我希望当第一个请求失败时,另一个应该自动从我列表中的下一个 url 开始。

这是一个示例,其中我使用两个静态地址尝试此行为
这个解决方案的问题是 url 在我开始下一个请求之前不会改变。 我希望 urls 在每次重试时发生变化

 public static void ConfigureUserServiceClient(this IServiceCollection services)
    {

        _userServiceUri = new Uri("https://localhost:5001");

        services.AddHttpClient("someService", client =>
        {
            client.BaseAddress = _userServiceUri;
            client.DefaultRequestHeaders.Add("Accept", "application/json");
        }).AddPolicyHandler(retryPolicy());
    }

    private static IAsyncPolicy<HttpResponseMessage> retryPolicy()
    {
        return Policy.HandleResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.RequestTimeout)
            .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(retryAttempt),
            onRetry: (result, span, ctx) =>
            {
                _userServiceUri = new Uri("https://localhost:5002");
            });
    }

您应该考虑改用 Fallback 政策。

像这样:

private static HttpClient client = new HttpClient();
static async Task Main(string[] args)
{
    var addressIterator = GetUrls().GetEnumerator();

    var retryLikePolicy = Policy<string>
        .Handle<HttpRequestException>()
        .FallbackAsync(fallbackAction: async (ct) =>
        {
            if (addressIterator.MoveNext())
               return await GetData(addressIterator.Current);
            return null;
        });

    addressIterator.MoveNext();
    var data = await retryLikePolicy.ExecuteAsync(
       async () => await GetData(addressIterator.Current));

    Console.WriteLine("End");
}

static async Task<string> GetData(string uri)
{
    Console.WriteLine(uri);
    var response = await client.GetAsync(uri);
    return await response.Content.ReadAsStringAsync();
}

static IEnumerable<string> GetUrls()
{
    yield return "http://localhost:5500/index.html";
    yield return "http://localhost:5600/index.html";
    yield return "http://localhost:5700/index.html";
}

请注意,此代码仅用于演示。


更新#1:多重回退

如果您有多个后备方案 url,那么您可以像这样修改上面的代码:

private static HttpClient client = new HttpClient();
static async Task Main(string[] args)
{
    var retryInCaseOfHRE = Policy
        .Handle<HttpRequestException>()
        .WaitAndRetryForeverAsync(_ => TimeSpan.FromSeconds(1));

    var response = await retryInCaseOfHRE.ExecuteAsync(
         async () => await GetNewAddressAndPerformRequest());
    
    if (response == null)
    {
        Console.WriteLine("All requests failed");
        Environment.Exit(1);
    }

    Console.WriteLine("End");
}

static IEnumerable<string> GetAddresses()
{
    yield return "http://localhost:5500/index.html";
    yield return "http://localhost:5600/index.html";
    yield return "http://localhost:5700/index.html";
    yield return "http://localhost:5800/index.html";
}

static readonly IEnumerator<string> AddressIterator = GetAddresses().GetEnumerator();

static async Task<string> GetNewAddressAndPerformRequest()
    => AddressIterator.MoveNext() ? await GetData(AddressIterator.Current) : null;

static async Task<string> GetData(string uri)
{
    Console.WriteLine(uri);
    var response = await client.GetAsync(uri);
    return await response.Content.ReadAsStringAsync();
}
  • 诀窍:重试策略包装了一个方法,该方法负责检索下一个 url 然后调用 GetData
    • 换句话说,我们需要将迭代过程移动到要包装的方法中(GetNewAddressAndPerformRequest)
  • 我已将 Fallback 策略替换为 Retry,因为我们需要执行(可能)超过 1 个回退操作
  • 我已经使用 null 来表示我们有 运行 回退 url,但使用自定义异常可能是更好的解决方案