前言: 最近, 同事在工作中遇到了使用 HttpClient, 有些请求超时的问题, 辅导员让我下去调研一下, HttpClinet 的使用方式已经改成了之前博客中提到的方式, 问题的原因我已经找到了, 就是因为使用了伪异步, 导致阻塞主线程. 在之前的博客中有园友, 建议在使用静态的 HttpClinet 时务必使用它的 Async 方法, 所以就得从头到尾异步化. 这一点在之前的文章中没有提, 这里作为补充, 也感谢这位园友. 关于怎么使用异步编程, 在这里我就不聊了, 大家可以看看其他的博客, 看完公司的代码之后, 我想强调的是, 在使用异步编程的时候, 关于返回值的问题:
为什么 async 方法返回的通常都是 Task 或者 Task<T>, 而不是 T 本身? 这是因为, Task 和 Task<T > 代表着在将来某一个时刻将会返回 T 类型的结果. 因此, 在主线程调用 HttpPostWhitStrBody 时, 实际上你拿到的是一个未来才会发生的预期, 也就是未来的某一个时间会得到一个 string 的结果. 如果返回的是一个 T 本身, 那么, 在主线程调用时就会因为访问这个需要一段时间才能给出结果, 从而阻塞了主线程. 因此, 如果 async 方法有返回值, 应返回 Task<T>. 如果没有返回值, 应该返回 Task. 大家如果不太明白的话, 建议多了解一下 C# 中的异步编程. 好了, 前戏太多了, 下面就来聊聊如何集成 Polly.
一, 在异步编程中如何处理异常信息
在聊如何集成 Polly 前, 我们先来看看在异步编程中如何处理异常. 当异步操作发生异常的时候, 异常会停留在异步方法中, 调用方法无法直接看到, 因此, 我们应该异步方法中处理异常, 而不是在调用方法中处理异常. 如果我们使用了 await 修饰了任务, 那么, 只需要为它包上一层 try-catch 就可以了. 当然了, 也可以在调用方法 (比如 Main 方法中) 捕捉异常, 这就需要异常从异步方法中传播给调用方法. 做到这件事是很容易的, 只需要两个条件:
(1)调用方法本身也是 async 的, 并且, 在内部调用异步方法, 并使用 await.
(2)异步方法返回 Task 或者 Task<T>
因为 C# 不允许在 Main 方法中使用 async(在 C#7.1 中, 可以使用 async 修饰 Main 方法了), 因此, 我们不得不再创建一层方法, 下面通过代码演示一下.
- using System;
- using System.Threading;
- using System.Threading.Tasks;
- namespace ConsoleApp3
- {
- class Program
- {
- static void Main(string[] args)
- {
- Caller();
- Console.ReadKey();
- }
- static async void Caller()
- {
- try
- {
- await UseAsync(0);
- }
- catch (AggregateException e)
- {
- Console.WriteLine(e.Message);
- }
- }
- static async Task<bool> UseAsync(int number)
- {
- Console.WriteLine("异步方法运行:"+ Thread.CurrentThread.ManagedThreadId);
- var ret = await Task.Run(() => IsPrimeLowAsync(number));
- return ret;
- }
- static bool IsPrimeLowAsync(int number)
- {
- if (number <= 0) throw new AggregateException("输入必须大于 0");
- if (number == 1) return false;
- for (int i = 0; i <number; i++)
- {
- if (number % i == 0) return false;
- }
- return true;
- }
- }
- }
- View Code
一般在处理异常的时候, 我们都是采用 try-catch 来做处理的, 若我们想重试三次, 此时我们只能进行循环三次操作. 我们只能简单进行处理, 自从有了 Polly, 什么重试机制, 超时都不在话下, 下面把话题转向 Polly.
在聊下面的话题时, 建议大家先认真阅读一下这篇博客, 因为博主讲的非常细致: Polly
二, 集成 Polly, 处理 HTTP 请求过程中的瞬时故障
Polly 是一种流行的瞬态故障处理库, 它提供了一种机制来定义可在某些故障发生时应用的策略. 最常用的策略之一就是重试策略. 这中策略允许您包装一些代码, 如果发生故障, 将重试这些代码; 必要时也可以重试多次. 这在您的应用程序需要与外部服务通信的情况下非常有用. 当通过 HTTP 与服务进行通信时, 会出现瞬态故障, 这种风险始终存在. 瞬态故障可能会阻碍您的请求完成, 但是瞬态故障也可能是暂时性的问题. 因此, 这使得在这些情况下重试成为明智的选择.
除了重试之外, Polly 还提供了许多其他类型的策略, 其中许多策略可能需要与重试相结合, 以构建处理故障的复杂方法. 我将在本文中介绍一些更一般的例子, 但是如果你想要更全面的了解, 我建议你查看一下 Polly wiki https://GitHub.com/App-vNext/Polly/wiki .
使用 Polly
ASP.NET 团队与 Polly 的主要维护者 Dylan 和 Joel 密切合作, 使得将 Polly 策略应用于 HttpClient 实例非常简单. 在开始之前我们先引用下面的两个包:
这个 Microsoft.Extensions.Http.Polly 包在 IHttpClientBuilder 上包含一个名为 AddPolicyHandler 的扩展方法, 我们可以使用它来添加一个 handler , 该 handler 将使用一个 Polly 实例, 来包装请求.
我们可以用这个扩展在我们的 ConfigureServices 方法中, 代码如下:
- services.AddHttpClient("GitHub")
- .AddPolicyHandler(Policy.TimeoutAsync<HttpResponseMessage>(TimeSpan.FromSeconds(10)));
在这个例子中, 我们定义了一个名字为 "GitHub" 的客户端, 并且我们使用 AddPolicyHandler 方法来添加了一种处理超时的策略, 这里提供的超时策略, 必须是 IAsyncPolicy<HttpResponseMessage>, 这个中策略在任何请求超过 10s 都会触发.
重试策略
如果可能的话, 当我们在使用 Polly 时, 最好的尝试是, 定义一次策略并在应用相同策略的情况下共享它们, 这样要更改策略, 只需在一个位置进行更改. 此外, 它还确保仅分配策略一次. 当然了, 如果多个使用者希望通过相同的断路器实例运行, 则需要共享诸如断路器之类的策略. 不太理解, 不要紧, 下面看代码, 体会一下.
- var retryPolicy = Policy.TimeoutAsync<HttpResponseMessage>(TimeSpan.FromSeconds(10));
- services.AddHttpClient("GitHub")
- .AddPolicyHandler(retryPolicy);
- services.AddHttpClient("google")
- .AddPolicyHandler(retryPolicy);
瞬时错误处理
处理 HTTP 请求时, 我们要处理的最常的问题就是瞬态故障. 由于这是一个常见的要求, Microsoft.Extensions.Http.Polly 软件包中包含一个特定的扩展, 我们可以使用它来快速设置处理瞬时故障的策略.
例如, 要在指定客户端的请求发生瞬时故障时添加基本重试, 我们可以按如下方式注册重试策略:
- services.AddHttpClient("GitHub")
- .AddTransientHttpErrorPolicy(p => p.RetryAsync(3));
代码的含义是, 所以使用命名的 HttpClient, 发出的请求, 只要遇到错误, 就会重试三次. 这个 AddTransientHttpErrorPolicy 方法需要一个 Func<PolicyBuilder<HttpResponseMessage>, IAsyncPolicy<HttpResponseMessage>>. 类型的参数. 此处的 PolicyBuilder 将预先配置为处理 HttpRequestExceptions, 任何返回 5xx 状态代码的响应以及具有 408(请求超时)状态代码的任何响应. 这应该适用于许多情况. 如果您要求在其他条件下应用策略, 则需要使用不同的重载来传递更具体的策略.
我们需要意识到, 在进行重试时, 我们需要考虑幂等性. 重试 HTTP GET 是一种非常安全的操作. 因为 HTTP GET 本身就是幂等性的, 如果我们调用一个方法但没有收到任何响应, 我们可以安全地重试调用而不会有任何危险. 但是, 请考虑如果我们重试 HTTP POST 请求会发生什么? 在这种情况下, 我们必须更加小心, 因为您的原始请求可能实际收到, 但我们收到的响应却显示失败. 在这种情况下, 重试可能导致数据重复或下游系统中存储的数据损坏. 在这里, 您需要更多地了解下游服务在多次收到相同请求时将执行的操作. 重试是一种安全操作? 当您拥有下游服务时, 更容易控制它. 例如, 您可以使用一些唯一标识符来防止重复的 POST.
如果您对下游系统的控制较少, 或者您知道重复的 POST 可能会产生负面影响, 则需要更仔细地控制策略. 可能适合的做法是定义不同的命名 / 类型客户端. 您可以为那些没有副作用的请求创建一个, 而为那些有副作用的请求创建另一个. 然后, 您可以使用正确的客户端进行操作. 但是, 这可能会变得有点难以管理. 更好的选择是使用 AddPolicyHandler 的重载, 它允许我们访问 HttpRequestMessage, 以便可以有条件地应用策略. 那个重载看起来像这样: AddPolicyHandler(Func<HttpRequestMessage, IAsyncPolicy<HttpResponseMessage>> policySelector), 您将注意到此处的 policySelector 委托可以访问 HttpRequestMessage, 并且应该返回 IAsyncPolicy <HttpResponseMessage>. 我们无法访问 PolicyBuilder 设置来处理瞬态错误, 就像我们在前面的示例中所做的那样. 如果我们想要处理常见的瞬态错误, 我们需要为我们的策略定义预期条件. 为了简化这一过程, Polly 项目包含一个帮助扩展, 我们可以使用它来设置一个准备好处理常见瞬态错误的 PolicyBuilder. 要使用扩展方法, 我们需要从 Nuget 添加 Polly.Extensions.Http 包.
然后, 我们可以调用 HttpPolicyExtensions.HandleTranisentHttpError()来获取配置瞬态故障条件的 PolicyBuilder. 我们可以使用该 PolicyBuilder 创建一个合适的重试策略, 当请求是 HTTP GET 时, 可以有条件地应用该策略. 在此示例中, 任何其他 HTTP 方法都使用 NoOp 策略.
- var retryPolicy = HttpPolicyExtensions
- .HandleTransientHttpError()
- .RetryAsync(3);
- var noOp = Policy.NoOpAsync().AsAsyncPolicy<HttpResponseMessage>();
- services.AddHttpClient("GitHub")
- .AddPolicyHandler(request => request.Method == HttpMethod.Get ? retryPolicy : noOp);
使用 PolicyRegistry
我想在本文中介绍的最后一个示例是如何从策略注册表中应用策略. 为了支持策略重用, Polly 提供了 PolicyRegistry 的概念, PolicyRegistry 本质上是策略的容器. 这些可以在应用程序启动时通过向注册表添加策略来定义. 然后可以传递注册表并用于按名称访问策略. IHttpClientBuilder 上可用的扩展还支持使用注册表将基于 Polly 的处理程序添加到客户端.
- var registry = services.AddPolicyRegistry();
- var timeout = Policy.TimeoutAsync<HttpResponseMessage>(TimeSpan.FromSeconds(10));
- var longTimeout = Policy.TimeoutAsync<HttpResponseMessage>(TimeSpan.FromSeconds(30));
- registry.Add("regular", timeout);
- registry.Add("long", longTimeout);
- services.AddHttpClient("GitHub")
- .AddPolicyHandlerFromRegistry("regular");
首先, 我们必须在 DI 中注册 PolicyRegistry. Microsoft.Extensions.Http.Polly 包中包含一些扩展方法, 以简化此操作. 在上面的示例中, 我调用 AddPolicyRegistry 方法, 该方法是 IServiceCollection 的扩展. 这将创建一个新的 PolicyRegistry, 并在 DI 中添加注册, 作为 IPolicyRegistry <string > 和 IReadOnlyPolicyRegistry <string > 的实现. 该方法返回策略, 以便我们有权向其添加策略.
在此示例中, 我们添加了两个超时策略并为其指定了名称. 现在, 在注册客户端时, 我们可以调用 IHttpClientBuilder 上的 AddPolicyHandlerFromRegistry 方法. 这将采用我们想要使用的策略的名称. 当工厂创建此命名客户端的实例时, 它将添加适当的处理程序, 在 "regular" 重试策略中包含调用, 该策略将从注册表中检索.
示例项目: 新建一个. Net Core 2.1 的 webapi 项目:
Startup.cs 文件的代码:
- public void ConfigureServices(IServiceCollection services)
- {
- services.AddHttpClient("GitHub", client =>
- {
- client.BaseAddress = new Uri("https://API.GitHub.co");
- client.DefaultRequestHeaders.Add("Accept", "application/vnd.GitHub.v3+JSON");
- client.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
- })
- .AddTransientHttpErrorPolicy(builder => builder.WaitAndRetryAsync(new[]
- {
- TimeSpan.FromSeconds(1),
- TimeSpan.FromSeconds(5),
- TimeSpan.FromSeconds(10)
- }));
- services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
- }
注意:
WaitAndRetryAsync 参数的意思是: 每次重试时等待的睡眠持续时间.
ValuesController 的代码:
- private readonly IHttpClientFactory _httpClientFactory;
- public ValuesController(IHttpClientFactory httpClientFactory)
- {
- _httpClientFactory = httpClientFactory;
- }
- // GET API/values
- [HttpGet]
- public async Task<ActionResult> Get()
- {
- var client = _httpClientFactory.CreateClient("GitHub");
- string result = await client.GetStringAsync("/");
- return Ok(result);
- }
看到没, 它在重试.
更多的 Polly 和 HttpClinetFactory 的集成使用请参考:
- https://GitHub.com/App-vNext/Polly/wiki/Polly-and-HttpClientFactory
- https://www.hanselman.com/blog/AddingResilienceAndTransientFaultHandlingToYourNETCoreHttpClientWithPolly.aspx
三, 总结
注意: AddTransientHttpErrorPolicy 方法会自动帮我们处理以下错误:
- (1)Network failures (System.NET.Http.HttpRequestException)
- (2)HTTP 5XX status codes (server errors)
- (3)HTTP 408 status code (request timeout)
通过这些库, 您可以轻松地启动并运行能够无缝处理瞬态故障的 HttpClient 实例. 有关更详细的 Polly 文档和示例, 建议您查看 Polly wiki. 这里只是聊了关于 HttpClientFactory 中集成 Polly 的基础用法, 关于更详细的使用请参考: https://www.cnblogs.com/CreateMyself/p/7589397.html, 好了今天就聊到这里, 该系列文章还有最后一篇, 对于 Polly 我也是刚接触, 至于项目中是否使用还要经过辅导员的审核, 希望对你有帮助, 谢谢.
参考文章:
(翻译)https://www.stevejgordon.co.uk/httpclientfactory-using-polly-for-transient-fault-handling
来源: https://www.cnblogs.com/runningsmallguo/p/9692001.html