Liam W
封面

在 .NET Core 使用 HttpClientFactory 和 Polly(中)

作者
王亮·发表于 3 年前

译者:王亮
作者:Polly 团队
原文:http://t.cn/EhZ90oq
声明:我翻译技术文章不是逐句翻译的,而是根据我自己的理解来表述的(包括标题)。其中可能会去除一些不影响理解但本人实在不知道如何组织的句子。

译者序:这是 Polly and HttpClientFactory 这篇 Wiki 文档翻译的中篇,你可以 点击这里查看上篇。接下来的两篇则是在这个基础上进行加强。本篇(中篇)主要讲如何在 ASP.NET Core 中通过 HttpClientFactory 配置 Polly 策略。如果你对 ASP.NET Core 2.1 新引入的 HttpClient 工厂还比较陌生,建议先阅读我的另一篇文章 .NET Core 中正确使用 HttpClient 的姿势,这有助于更好地理解本文。

-- 正文 --

下面主要讲如何在 ASP.NET Core 中通过 HttpClientFactory 配置 Polly 策略。

使用 AddTransientHttpErrorPolicy

让我们先回到上篇的例子:

services.AddHttpClient("GitHub", client =>
{
    client.BaseAddress = new Uri("https://api.github.com/");
    client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
})
.AddTransientHttpErrorPolicy(builder => builder.WaitAndRetryAsync(new[]
{
    TimeSpan.FromSeconds(1),
    TimeSpan.FromSeconds(5),
    TimeSpan.FromSeconds(10)
}));

这里用了一个新的 AddTransientHttpErrorPolicy 方法,它可以很方便地配置一个策略来处理下面这些典型的 HTTP 调用错误:

  • 网络错误(HttpRequestException 异常)
  • HTTP 状态码 5XX(服务器错误)
  • HTTP 状态码 408(请求超时)

AddTransientHttpErrorPolicy 方法添加了一个策略,这个策略默认预配置了上面 HTTP 错误的过滤器。在 builder => builder 子句中,你可以定义策略如何处理这些错误,还可以配置 Polly 提供的其它策略,比如重试(如上例所示)、熔断或回退等。

AddTransientHttpErrorPolicy 中处理网络错误、HTTP 5XX 和 HTTP 408 是一种便捷的方式,但这不是必需的。如果此方法内置的错误过滤器不适合您的需要(你需要仔细考虑一下),您可以扩展它,或者构建一个完全定制的 Polly 策略。

扩展 AddTransientHttpErrorPolicy

AddTransientHttpErrorPolicy 方法也可以从 Polly 的一个扩展包 Polly.Extensions.Http 中得到,它在上面的基础上进行了扩展。例如下面配置的策略可以处理 429 状态码:

using Polly.Extensions.Http;

// ...

var policy = HttpPolicyExtensions
  .HandleTransientHttpError() // HttpRequestException, 5XX and 408
  .OrResult(response => (int)response.StatusCode == 429) // RetryAfter
  .WaitAndRetryAsync(/* etc */);

使用典型 Polly 语法配置好的策略

Polly 还有另一个扩展方法是 AddPolicyHandler,它的一个重载方法可以接收任意 IAsyncPolicy 参数,所以你可以用典型的 Polly 语法先定义好任意的一个策略(返回类型为 IAsyncPolicy),然后再传给 AddPolicyHandler 扩展方法。

下面这个例子演示了用 AddPolicyHandler 来添加一个策略,其中我们编写了自己的错误处理策略:

var retryPolicy = Policy.Handle<HttpRequestException>()
    .OrResult<HttpResponseMessage>(response => MyCustomResponsePredicate(response))
    .WaitAndRetryAsync(new[]
    {
        TimeSpan.FromSeconds(1),
        TimeSpan.FromSeconds(5),
        TimeSpan.FromSeconds(10)
    }));

services.AddHttpClient("GitHub", client =>
{
    client.BaseAddress = new Uri("https://api.github.com/");
    client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
})
.AddPolicyHandler(retryPolicy);

类似的,你还可以配置其它策略,比如超时策略:

var timeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(10);

services.AddHttpClient(/* etc */)
    .AddPolicyHandler(timeoutPolicy);

所有通过 HttpClient 的调用返回的都是一个 HttpResponseMessage 对象,因此配置的策略必须是 IAsyncPolicy 对象(译注:HTTP 请求返回的是 HttpResponseMessage 对象,Polly 定义的策略是一个 IAsyncPolicy 对象,所以 AddPolicyHandler 方法接收的参数是这两者的结合体 IAsyncPolicy 对象)。非泛型的 IAsyncPolicy 可以通过下面的方式转换成泛型的 IAsyncPolicy

var timeoutPolicy = Policy.TimeoutAsync(10);

services.AddHttpClient(/* etc */)
    .AddPolicyHandler(timeoutPolicy.AsAsyncPolicy<HttpResponseMessage>());

应用多个策略

所有策略配置的方法也可以链式地配置多个策略,例如:

services.AddHttpClient(/* etc */)
    .AddTransientHttpErrorPolicy(builder => builder.WaitAndRetryAsync(new[]
    {
        TimeSpan.FromSeconds(1),
        TimeSpan.FromSeconds(5),
        TimeSpan.FromSeconds(10)
    }))
    .AddTransientHttpErrorPolicy(builder => builder.CircuitBreakerAsync(
        handledEventsAllowedBeforeBreaking: 3,
        durationOfBreak: TimeSpan.FromSeconds(30)
    ));

多个策略被应用的顺序

当您配置多个策略时(如上例所示),策略应用于从外部(第一次配置)到内部(最后配置)的顺序依次调用。在上面的示例中,调用的顺序是这样的:

  1. 首先通过(外部)重试策略,该策略将依次:
  2. 通过(内部)熔断策略的调用,该策略将依次:
  3. 进行底层 HTTP 调用。

这个示例之所以用此顺序的策略是因为当重试策略在两次尝试之间等待时,熔断器可能在其中一个时间段(1、5 或 10 秒)内改变状态(译注:上面示例中熔断策略是出现 3 次异常就“休息”30 分钟)。熔断策略被配置在重试策略的内部,因此每执行一次重试就会执行其内部的熔断策略。

上面的例子应用了两个策略(重试和熔断),任意数量的策略都是可以的。一个常见的多个策略组合可能是这样的:重试、熔断和超时(“下篇”会有例子)。

对于那些熟悉 Polly 的策略包的人来说,使用上面的方式配置多个策略完全等同于使用策略包,也适用于所有“策略包的使用建议”(链接:http://t.cn/EhJ4jfN)。

动态选择策略

AddPolicyHandler 的重载方法允许你根据 HTTP 请求动态选择策略。

其中一个用例是对非等幂的操作应用不同的策略行为(译注:“等幂“指的是一个操作重复使用,始终都会得到同一个结果)。对于 HTTP 请求来说,POST 操作通常不是幂等的(译注:比如注册),PUT 操作应该是幂等的。所以对于给定的 API 可能不是一概而论的。比如,您可能想要定义一个策略,让它只重试 GET 请求,但不重试其他 HTTP 谓词,比如这个示例:

var retryPolicy = HttpPolicyExtensions
    .HandleTransientHttpError()
    .WaitAndRetryAsync(new[]
    {
        TimeSpan.FromSeconds(1),
        TimeSpan.FromSeconds(5),
        TimeSpan.FromSeconds(10)
    });
var noOpPolicy = Policy.NoOpAsync().AsAsyncPolicy<HttpResponseMessage>();

services.AddHttpClient(/* etc */)
    // 如果是GET请求,则使用重试策略,否则使用空策略
    .AddPolicyHandler(request => request.Method == HttpMethod.Get ? retryPolicy : noOpPolicy);

上面的空策略会被应用于所有非 GET 的请求。空策略只是一种占坑模式,实际上不做任何事情。

从策略的注册池中选择策略

Polly 还提供了策略注册池(请参阅:http://t.cn/Ehi1SQp ),它相当于策略的存储中心,被注册的策略可以让你在应用程序的多个位置重用。AddPolicyHandler 的一个重载方法允许您从注册池中选择策略。

下面的示例使用 IServiceCollection 添加一个策略注册池服务,向注册池中添加一些策略,然后使用注册池中的不同策略定义两个调用逻辑。

var registry = services.AddPolicyRegistry();

registry.Add("defaultretrystrategy",
    HttpPolicyExtensions.HandleTransientHttpError().WaitAndRetryAsync(/* etc */));
registry.Add("defaultcircuitbreaker",
    HttpPolicyExtensions.HandleTransientHttpError().CircuitBreakerAsync(/* etc */));

services.AddHttpClient(/* etc */)
    .AddPolicyHandlerFromRegistry("defaultretrystrategy");

services.AddHttpClient(/* etc */)
    .AddPolicyHandlerFromRegistry("defaultretrystrategy")
    .AddPolicyHandlerFromRegistry("defaultcircuitbreaker");

这个示例演示了从注册池中选择一个或多个策略应用在不同的 HttpClient 上,同一个策略被重复使用了两次。策略注册池的更复杂用例包括从外部动态更新注册池中的策略,以便在运行期间动态重新配置策略(请查阅 http://t.cn/Ehidgqy 了解更多)。

--

相关阅读:

  1. .NET 开源项目 Polly 介绍
  2. 在 .NET Core 使用 HttpClientFactory 和 Polly(上)