ASP.NET Core 2.1 中的 HttpClientFactory (Part 1) HttpClientFactory介绍

原文:https://www.stevejgordon.co.uk/introduction-to-httpclientfactory-aspnetcore  
发表于:2018年1月

ASP.NET Core 2.1中将出现一个新的HttpClientFactory功能,它有助于解决开发人员在使用HttpClient实例时可能遇到的一些问题。

介绍

      我从2017年11月中旬开始准备写这篇文章,当时我第一次注意到有一个新的 HttpClientFactory 版本库 出现在GitHub上。我对它的出现感到好奇,并且想知道 ASP.NET 团队在做什么,所以我深入研究了当时存储库中的代码。从那以后我一直留意这个问题,关注代码更新、问题反馈和社区讨论,看着开发团队不断完善其功能。

      最近,该功能开始被更多的讨论,并且由Damian Edwards和David Fowler在NDC伦敦举行的一次演讲中提及。事实上,在撰写此介绍的那一天,它已经在Jeff Fritz的直播节目ASP.NET Community Standup上展示。Ryan Nowak是该功能的主要开发人员之一,他认为功能已足够稳定,可以向大家展示了。

注意:这篇文章是在.NET Core 2.1的官方预览版之前使用ASP.NET Core 2.1和.NET Core SDK的每晚构建版本编写的。因此,根据从这些预览中收到的反馈,在公开预览之前和期间(希望我们将在下个月内获得这些内容)以及2.1的最终发布之前,可能会发生变化。

什么是 HttpClientFactory?

      用ASP.NET团队的话说,它是“一个用于创建HttpClient实例的自以为是的工厂”,并且是ASP.NET Core 2.1发布的新功能。根据您过去使用HttpClient的经验,您可能遇到过一些陷阱,或者可能没有意识到存在问题。

      第一个问题是当你在代码中创建太多的HttpClients时,这会带来两个负面问题:

  1. 效率不高,因为每连接都有自己的远程服务器连接池。这意味着您需要为创建的每个客户端重新连接到该远程服务器支付额外开销。
  2. 可能遇到的更大问题是,如果你在短时间内创建了大量客户端,可能会遇到Socket耗尽问题。在一定时间内可以使用的Socket是有限制的。当你使用HttpClient打开连接之后,它会保持最长240秒的TIME_WAIT状态(这期间来自远程服务器的任何数据包仍然通过)。

      HttpClient实现了IDisposable,通常开发人员在使用IDisposable对象时会在using块中创建它,这样可以确保对象在使用完后被释放掉。如果你想阅读更多这方面的信息,ASP.NET Monsters在他们的文章“你正在错误的使用HttpClient,它使你的软件失去稳定性”中有很好的叙述。

      通常,首选方法是重用HttpClient实例,以便可以重用连接。 HttpClient是一个可变对象(mutable object),但只要你没有改变它,它实际上是线程安全的并且可以共享。因此,常见的方法是通过DI框架注册为单例,或者为其创建一个容器成为静态实例。

      但是,这会产生新问题。这种方式并不遵守DNS生存时间(TTL)设置,连接将永远不会获得DNS更新,您与之通信的服务器永远不会更新地址。

      在某些情况下,有可能使用多个主机(Hosts)做负载均,随着时间的推移,一些主机会消失,一些主机新加入进来。如果主机消失,您的单例HttpClient连接的IP地址则不会响应您的请求。您可以在“单例HttpClient?必须小心使用以及如何解决”和“单例HttpClient不遵从DNS更新”阅读更多此类问题的信息。

      HttpClientFactory被设计用来解决这些问题,并提供一种新的后台机制,来管理和创建HttpClient实例。它会为我们做“该做的事情”,以便我们可以专注于其它事情。虽然,上面问题都指向HttpClient,但实际上问题的根源是在HttpClient使用的HttpClientHandler上。HttpClientFactory 用来管理Handlers的生命周期,以便我们可以重用池(pool),同时保证DNS不会过期。

      使用HttpClient消耗最大的部分实际上是创建HttpClientHandler和连接(Connection)。把它们放到池(pool)中是为了在系统中更加高效的使用他们。当我们是使用HttpClientFactory请求一个HttpClient时,实际上每次都会得到一个新的实例,这意味这我们不用担心会改变(mutating)它的状态。HttpClient可能使用(也可能不使用)池(pool)中已有的HttpClientHandler来保持连接。

      默认情况下,每个新的HttpClientHandler(派生自HttpMessageHandler)将以2分钟的生命周期创建。在创建它的处理程序链(handler chain)时,可以在每个命名的客户端上控制它。达到生命周期后,处理程序(handler)将不会立即被释放,而是放入过期的池中。任何基于原始处理程序链(original handler chain)的客户端都可以继续使用它。有一个后台作业检查过期的池,以查看处理程序的所有引用是否已超出范围,然后可以将其处理掉。处理程序链(handler chain)过期后对新客户端的任何新请求都将获得新的处理程序链。

      这种方法能够很好的工作,但.NET Core还会更进一步。.NET Core团队正在开发一个新的ManagedHandler,它可以更好地管理DNS,原则上可以保持更长时间,这意味着可以更有效地共享连接。这个新的处理程序(handler)也被设计为在不同的操作系统中更一致地运行。在该工作完成之前(可能在2.1时间范围内),上面的处理程序池是一个合理的解决方法。

如何使用HttpClientFactory

重要说明:下面的功能和代码示例需要SDK每晚构建版本以及.NET Core和ASP.NET Core库,我不会介绍如何设置和使用它们。仅为展示该功能如何工作,以便您可以在2.1正式发布时考虑是否使用它们。除非您今天迫切需要尝试这一点,否则我建议您等到2.1预览发布,希望在下个月左右。

      本节中,我将主要介绍HttpClientFactory的最基本用法。我们将创建一个简单的WebAPI项目,然后编辑csproj文件以将其升级为使用新的.NET Core和ASP.NET Core 2.1。首先,我们需要将其设置为基于netcoreapp2.1(尚未在官方预览中),然后包含我们需要的两个包,我们的项目文件如下所示:

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>netcoreapp2.1</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.All" Version="2.1.0-preview1-28124" />
    <PackageReference Include="Microsoft.Extensions.Http" Version="2.1.0-preview1-28124" />
  </ItemGroup>
  
</Project>

      接下来,我们需要在Startup.cs注册服务。 HttpClientFactory有多种ServiceCollection扩展方式。我们使用其中的一种:

services.AddHttpClient();

      这会注册一些必需的服务,其中一个将是IHttpClientFactory的实现。接下来,我们更新ValuesController以使用此功能:

[Route("api/[controller]")]
public class ValuesController : Controller
{
    private readonly IHttpClientFactory _httpClientFactory;

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

    [HttpGet]
    public async Task<ActionResult> Get()
    {
        var client = _httpClientFactory.CreateClient();
        var result = await client.GetStringAsync("http://www.google.com");
        return Ok(result);
    }
}

      这里我们首先添加对IHttpClientFactory的依赖,它将由DI系统注入我们的控制器。 IHttpClientFactory允许我们请求和接收HttpClient实例。

      在我们的Get操作中,我们使用HttpClientFactory创建客户端。在其内部,HttpClientFactory将为我们创建一个新的HttpClient。但是,之前我不是说过为每个请求创建新的HttpClient是很糟糕的吗?但实际上这有点误导。HttpClient本身并不是真正的问题,而是用于实现HTTP调用的HttpClientHandler,这才是实际问题。HttpClientHandler用来打开与外部服务的连接,这些连接将保持打开并阻止sockets,即使主HttpClient被释放之后也是如此。

      HttpClientFactory汇集这些HttpClientHandler实例并管理它们的生命周期,以解决我之前提到的一些问题。每次我们请求HttpClient时,我们都会得到一个新实例,它可能(或可能不)使用现有的HttpClientHandler。HttpClient本身并不太重,所以这没关系。

      一旦创建,HttpClientHandlers就会被放置到池(pool)中,默认情况下会保持约2分钟。这意味着任何一个新的CreateClient请求都可以共享一个处理程序,因此也可以共享连接。当HttpClient存在时,它的处理程序将保持可用,并且共享连接。

      两分钟后,每个HttpClientHandler都标记为已过期。过期状态只是标记,以便在创建任何新的HttpClient实例时不再使用它们。但是,它们不会立即处理,因为其他HttpClient实例可能正在使用它们。 HttpClientFactory使用后台服务来监视过期的处理程序,一旦它们不再被引用,就可以正确处理它们,也允许它们关闭连接。

      池(pooling)有助于降低socket耗尽的风险,其刷新机制可以处理过长生命周期的HttpClientHandlers实例和挂起的连接,从而解决DNS更新问题。这是一个合理的折衷方案。

总结

      本文就介绍到这里。在以后的文章中,我将深入探讨一些HttpClientFactory的高级方法,因为有一些很好的功能值得展示。看看如何通过配置创建命名的HttpClient实例,以及创建自己的类型化客户端。这是该功能真正的亮点。希望您已经了解在这个基本示例中,它是如何以最正确和有效的方式处理HTTP调用来满足我们的用例。我们不需要考虑如何管理客户端的生命周期或担心遇到DNS问题。我希望在ASP.NET Core 2.1正式发布后,这些功能能够应用到生产环境中去。

posted @ 2019-06-27 16:58  lookerblue  阅读(405)  评论(0编辑  收藏  举报