使用 IHttpClientFactory 实现复原 HTTP 请求

Polly 是一个 .NET 库,提供恢复能力和瞬态故障处理功能。 通过应用 Polly 策略(如重试、断路、隔离、超时和回退)即可实现这些功能。 

断路:当系统出错的次数超过了指定的阈值,就要中断当前线路,等待一段时间后再继续。

隔离:将可控的操作限制在一个固定大小的资源池中,以隔离有潜在可能相互影响的操作。

 

HttpClient类实现 IDisposable,但在 using 语句中声明和实例化它并非首选操作,因为释放 HttpClient 对象时,基础套接字不会立即释放,这可能会导致套接字耗尽问题。 在长期运行的进程中使用 HttpClient 的共享实例时,开发人员会遇到另一个问题。 在将 HttpClient 实例化为单一实例或静态对象的情况下,它无法处理 DNS 更改,问题实际上不是 HttpClient 本身,而是 HttpClient 的默认构造函数,因为它创建了一个新的实际 HttpMessageHandler 实例,该实例具有上面提到的“套接字耗尽”和 DNS 更改问题 。

要解决上述问题并使 HttpClient 实例可管理,.NET Core 2.1 引入了 IHttpClientFactory 接口,该接口可用于在应用中通过依赖关系注入 (DI) 来配置和创建 HttpClient 实例。 它还提供基于 Polly 的中间件的扩展,以利用 HttpClient 中的委托处理程序。

使用 IHttpClientFactory 的好处

同时实现 IHttpMessageHandlerFactory 的 IHttpClientFactory 当前实现具有以下优势:

  • 提供一个中心位置,用于命名和配置逻辑 HttpClient 对象。 例如,可以配置预配置的客户端(服务代理)以访问特定微服务。
  • 通过后列方式整理出站中间件的概念:在 HttpClient 中委托处理程序并实现基于 Polly 的中间件以利用 Polly 的复原策略。
  • HttpClient 已经具有委托处理程序的概念,这些委托处理程序可以链接在一起,处理出站 HTTP 请求。 将 HTTP 客户端注册到工厂后,可使用一个 Polly 处理程序将 Polly 策略用于重试、断路器等。
  • 管理 HttpMessageHandler 的生存期,避免在自行管理 HttpClient 生存期时出现上述问题。

由于关联的 HttpMessageHandler 由工厂管理,因此可安全释放由 DI 注入的 HttpClient 实例。 事实上,注入的 HttpClient 实例是从 DI 角度区分范围的 。

IHttpClientFactory (DefaultHttpClientFactory) 实现与 Microsoft.Extensions.DependencyInjection NuGet 包中的 DI 实现紧密关联。 

 

结合使用类型化客户端和 IHttpClientFactory

什么是“类型化客户端”? 它只是为某些特定用途预配置的 HttpClient。 此配置可以包括特定值,如基本服务器、HTTP 标头或超时。

展示如何将类型化客户端与 IHttpClientFactory 结合使用的图表。

安装包含 IServiceCollection 的 AddHttpClient 扩展方法的 Microsoft.Extensions.Http NuGet 包,在应用程序中添加 IHttpClientFactory。 此扩展方法用于注册内部 DefaultHttpClientFactory 类,后者用作接口 IHttpClientFactory 的单一实例。 它定义 HttpMessageHandlerBuilder 的临时配置。 此消息处理程序(HttpMessageHandler 对象)获取自池,可供从工厂返回的 HttpClient 使用。

池中的 HttpMessageHandler 对象的生存期就是池中的 HttpMessageHandler 实例可重用的时间长度。 默认值为两分钟,但可基于每个类型化客户端重写此值。 要重写该值,请在创建客户端时在返回的 IHttpClientBuilder 上调用 SetHandlerLifetime(),如以下代码所示:

//Set 5 min as the lifetime for the HttpMessageHandler objects in the pool used for the Catalog Typed Client
services.AddHttpClient<ICatalogService, CatalogService>()
    .SetHandlerLifetime(TimeSpan.FromMinutes(5));

还可以在注册中添加特定于实例的配置(例如,配置基址),并添加一些弹性策略,

services.AddHttpClient<ICatalogService, CatalogService>(client =>
{
    client.BaseAddress = new Uri(Configuration["BaseUrl"]);
})
    .AddPolicyHandler(GetRetryPolicy())
    .AddPolicyHandler(GetCircuitBreakerPolicy());

static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
{
    return HttpPolicyExtensions
        .HandleTransientHttpError()
        .OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.NotFound)
        .WaitAndRetryAsync(6, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
}

 GetRetryPolicy 用到了Microsoft.Extensions.Http.Polly 扩展包

 

 IHttpClientFactory允许你用命名的方式来配置和使用 HttpClient。

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)
}));

public class MyController : Controller
{
    private readonly IHttpClientFactory _httpClientFactory;

    public MyController(IHttpClientFactory httpClientFactory)
    {
        _httpClientFactory = httpClientFactory;
    }

    public Task<IActionResult> SomeAction()
    {
        var client = _httpClientFactory.CreateClient("GitHub");
        return Ok(await client.GetStringAsync("/someapi"));
    }
}

AddTransientHttpErrorPolicy方法也可以从Polly的一个扩展包Polly.Extensions.Http中得到

 

Polly还提供了策略注册池,它相当于策略的存储中心,被注册的策略可以让你在应用程序的多个位置重用。AddPolicyHandler的一个重载方法允许您从注册池中选择策略。

var registry = services.AddPolicyRegistry();

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

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

 

将抖动策略添加到重试策略

在高并发率、高可伸缩性和高争用的情况下,常规重试策略可能会对系统产生影响。 在部分运行中断的情况下,有可能会有许多客户端同时发出相似的重试操作,从而形成操作高峰,为克服这种情况,一个好办法是向重试算法或策略中添加抖动策略。 由于增加了指数退避的随机性,这可能会改进端到端系统的整体性能。 这样在出现问题时可以分散峰值。

Random jitterer = new Random();
var retryWithJitterPolicy = HttpPolicyExtensions
    .HandleTransientHttpError()
    .OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.NotFound)
    .WaitAndRetryAsync(6,    // exponential back-off plus some jitter
        retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))  
                      + TimeSpan.FromMilliseconds(jitterer.Next(0, 100))
    );

 

 

posted @ 2020-09-24 16:06  yetsen  阅读(548)  评论(0编辑  收藏  举报