Liam W
封面

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

作者
王亮·发表于 3 年前

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

译者序:这是 Polly and HttpClientFactory 这篇 Wiki 文档翻译的下篇。你可以 点击这里查看上篇,和 点击这里查看中篇。本篇(下篇)主要讲几个 Polly 和 HttpClientFactory 在 ASP.NET Core 中结合使用的用例。如果你对 ASP.NET Core 2.1 新引入的 HttpClient 工厂还比较陌生,建议先阅读我的另一篇文章 .NET Core 中正确使用 HttpClient 的姿势,这有助于更好地理解本文。

-- 正文 --

下面主要讲几个 Polly 和 HttpClientFactory 在 ASP.NET Core 中结合使用的用例。

用例:应用超时策略

HttpClient 已经有了一个 Timeout 属性,但是在使用重试策略时该如何应用呢?Polly 的超时策略又适用于什么地方?

  • HttpClient.Timeout 属性设置的超时将被应用于 HttpClient 实例的所有调用,包括重试之间的所有尝试和等待。

  • 要在每次重试中使用超时,就要在 Polly 的超时策略之前配置重试策略。

在这种情况下,你可能希望重试策略在每次单个超时时重试。为此,需要让重试策略处理超时策略抛出的 TimeoutRejectedException 异常。

下面这个示例使用了上篇提到的 Polly.Extensions.Http 这个包,它可以很方便地为 Http 错误(比如 HttpRequestException、Http 5XX 和 Http 408 等)添加额外的处理。

using Polly.Extensions.Http;

var retryPolicy = HttpPolicyExtensions
    .HandleTransientHttpError()
    .Or<TimeoutRejectedException>() // 若超时则抛出此异常
    .WaitAndRetryAsync(new[]
        {
            TimeSpan.FromSeconds(1),
            TimeSpan.FromSeconds(5),
            TimeSpan.FromSeconds(10)
        });

// 为每个重试定义超时策略
var timeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(10);

serviceCollection.AddHttpClient("GitHub", client =>
{
    client.BaseAddress = new Uri("https://api.github.com/");
    client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
    client.Timeout = TimeSpan.FromSeconds(60); // 默认超时时间
})
.AddPolicyHandler(retryPolicy)
// 将超时策略放在重试策略之内,每次重试会应用此超时策略
.AddPolicyHandler(timeoutPolicy);

用例:缓存策略

Polly 的缓存策略可以在通过 IHttpClientFactory 配置的委托处理程序中使用。Polly 是通用的(不与 Http 请求绑定),因此在编写代码时,Polly 缓存策略从 Polly.Context 中确定要使用的缓存键。可以通过 HttpRequestMessage 请求上的一个扩展方法来设置这个参数:

request.SetPolicyExecutionContext(new Polly.Context("CacheKeyToUseWithThisRequest"));

由于 Polly 缓存策略是在 HttpResponseMessage 级别的委托代理服务上进行缓存,因此还需要考虑下面的问题。

HttpResponseMessage 级别的缓存是否合适?

如果你想重用 HttpResponseMessage,那么在 HttpResponseMessage 级别上进行缓存可能非常合适。

但在某些情况下,比如调用 WebService 来获取一些序列化数据,然后反序列化到应用程序中的本地类型,HttpResponseMessage 可能不是缓存的最佳粒度。

在这些情况下,HttpResponseMessage 级别上的缓存意味着每次命中缓存都会重复读取数据流和反序列化,这在性能方面是不必要的。

在更高级别缓存可能更合适——例如,缓存流或反序列化到应用程序的本地类型的结果。

缓存 HttpResponseMessage 还要考虑以下三点:

  • HttpResponseMessage 可以包含 HttpContent,它只能向前读取流(只能读取一次)。这可能意味着,当 CachePolicy 第二次从缓存中检索它时,除非重新初始化流指针,否则无法重新读取流。

  • 考虑去个性化和时间戳。缓存的个人特有信息和时间戳可能不适合重新提供给后续的请求。

  • 注意只缓存状态码为 200(OK)的响应。考虑使用 Response.EnsureSuccessStatusCode() 等方法确保只有成功的响应才能传递给缓存策略。或者你可以使用这里描述的自定义 ITtlStrategy。

用例:在策略执行和调用之间交换信息

Polly 策略的每次执行都会携带 Polly.Context 类的一个执行域实例(execution-scoped instance),该类的作用是提供上下文,并允许在执行前、执行中和执行后阶段之间交换信息(译注:类似于 HttpContext)。

对于通过 HttpClientFactory 和 Polly 配置的 HttpClient,可以在执行之前使用扩展方法 HttpRequestMessage.SetPolicyExecutionContext(context) 来设置被用于 Http 调用的上下文 Polly.Context。该上下文具有字典语义,允许您传递任意数据。

var context = new Polly.Context();
context["MyCustomData"] = foo;

HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestUri);
request.SetPolicyExecutionContext(context);

var response = await client.SendAsync(request, cancellationToken);

Polly 将该上下文实例作为输入参数传递给策略上配置的任何委托钩子(例如 onRetry)。例如下面这个已经预先配置了策略的 HttpClient:

var retryPolicy = HttpPolicyExtensions
    .HandleTransientHttpError()
    .WaitAndRetryAsync(new[]
    {
        TimeSpan.FromSeconds(1),
        TimeSpan.FromSeconds(5),
        TimeSpan.FromSeconds(10)
    },
    onRetryAsync: async (outcome, timespan, retryCount, ctx) => {
        /* Do something with ctx["MyCustomData"] */
        // ...
    });

委托钩子可以在执行期间设置其上下文信息:

var retryPolicy = HttpPolicyExtensions
    .HandleTransientHttpError()
    .WaitAndRetryAsync(new[]
    {
        TimeSpan.FromSeconds(1),
        TimeSpan.FromSeconds(5),
        TimeSpan.FromSeconds(10)
    },
    onRetryAsync: async (outcome, timespan, retryCount, ctx) => {
        ctx["RetriesInvoked"] = retryCount;
        // ...
    });

这些信息可以在执行后从上下文中读取:

var response = await client.SendAsync(request, cancellationToken);

var context = response.RequestMessage?.GetPolicyExecutionContext(); // 如果还没有保存在局部变量中
if (context?.TryGetValue("RetriesInvoked", out int? retriesNeeded) ?? false)
{
    // Do something with int? retriesNeeded
}

注意,只有在执行之前使用 HttpRequestMessage.SetPolicyExecutionContext(context) 设置了上下文时,HttpRequestMessage.GetPolicyExecutionContext() 的获得的上下文才可用。

--

相关阅读:

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