优雅通过HttpClientFactory使用HttpClient

作者 : 开心源码 本文共5998个字,预计阅读时间需要15分钟 发布时间: 2022-05-12 共196人阅读

原文链接:https://blog.zhuliang.ltd/back-end/configurationBuilder-in-netCore/

从Task起,终于在.NET CORE 2.1 等到 HttpClient 的Pool。。。

HttpClient 日常使用及坑点:

在C#中,平常我们在使用HttpClient的时候,会将HttpClient包裹在using内部进行公告和初始化,如:

using(var httpClient = new HttpClient()){    //other codes}

至于为什么?无外乎是:项目代码中就是这样写的,依葫芦画瓢/别人就是这样用的/在微软官方的ASP.NET教程中也是这么干的。

说的技术范点:当你使用继承了IDisposable接口的对象时,建议在using代码块中公告和初始化,当using代码段执行完成后,会自动释放该对象而不需要手动进行显示Dispose操作。

但这里,HttpClient这个对象有点特殊,尽管继承了IDisposable接口,但它是可以被共享的(或者者说可以被复用),且线程安全。从项目经验来看,倒是建议在整个应用的生命周期内,复用HttpClient实例,而不是每次RPC请求的时候就实例化一个。(之前在优化公司一个web项目的时候,也曾经由于HttpClient载过一次坑,后面我会进行简述。)

我们先来用个简单的例子做下测试,看为什么不要每次RPC请求都实例化一个HttpClient:

  public class Program    {        static void Main(string[] args)        {            HttpAsync();            Console.WriteLine("Hello World!");            Console.Read();        }        public static async void HttpAsync()        {            for (int i = 0; i < 10; i++)            {                using (var client = new HttpClient())                {                    var result = await client.GetAsync("http://www.baidu.com");                    Console.WriteLine($"{i}:{result.StatusCode}");                }            }        }    }

运行项目输出结果后,通过netstate查看下TCP连接情况:

e92bde19-eae0-41a4-83ed-08c2038fdd0f.png

  • 尽管项目已经运行结束,但是连接仍然存在,状态为” TIME_WAIT”(继续等待看能否还有推迟的包会传输过来。)。

默认在windows下,TIME_WAIT状态将会使系统将会保持该连接 240s。

  • 这里也就引出了我上面说的载过的一次坑:在高并发的情况下,连接来不及释放,socket被耗尽,耗尽之后就会出现喜闻乐见的一个错误:
#使用jemter压测复现错误信息:Unable to connect to the remote serverSystem.Net.Sockets.SocketException: Only one usage of each socket address (protocol/network address/port) is normally permitted.

说白话:就是会出现“各种套接字问题”。(码WCF的童鞋可能更加记忆尤新,问题追根溯源都是换汤不换药。)

熊厂里面能够搜索出来的处理方法,基本都是“减少超时时间”,但人为减少了超时时间会出现各种莫名其妙的错误。且无法避免服务器迟早崩溃的问题。

可以通过注册表进行修改默认值:[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\TcpTimedWaitDelay])

那么如何解决这个问题?答案已经在上面说了,“复用HttpClient”就可。如:

    public class Program    {        private static readonly HttpClient _client = new HttpClient();        static void Main(string[] args)        {            HttpAsync();            Console.WriteLine("Hello World!");            Console.Read();        }        public static async void HttpAsync()        {            for (int i = 0; i < 10; i++)            {                var result = await _client.GetAsync("http://www.baidu.com");                Console.WriteLine($"{i}:{result.StatusCode}");            }        }    }

232f7b9e-b9f3-4444-9697-e0108c3b09fe.png

  • 可以看到,原价10个连接变成了1给连接。(请不要在意两次示例的目标IP不同—SLB导致的,都是百度的ip)

  • 另外,由于复用了HttpClient,每次RPC请求的时候,实际上还节约了创立通道的时间,在性能压测的时候也是很显著的提升。曾经由于这一举动,将web项目的TPS从单台600瞬间提升到了2000+,页面请求时间也从1-3s减少至100-300ms,甚是让测试组小伙伴膜拜(当然也包括了少量业务代码的细调。),但知道个中缘由后,一个小改动带来的项目性能提升。。。会让人上瘾:)

  • 至于如何创立一个静态HttpClient进行复用,大家可以按项目实际来,如直接创立一个“全局”静态对象,或者者通过各类DI框架来创立均可。

但这么调整HttpClient的引用后,仍然存在少量问题可能会影响到你的项目(尚未影响到我:P),如:

  • 由于是复用的HttpClient,那么少量公共的设置就没办法灵活的调整了,如请求头的自己设置。
  • 由于HttpClient请求每个url时,会缓存该url对应的主机ip,从而会导致DNS升级失效(TTL失效了)

那么有没有办法处理HttpClient的这些个问题?直到我遇到了 HttpClientFactory,瞬间写代码幸福感倍升,也感慨新时代的童鞋们真的太幸福了,老一辈踩的坑可以“完美”规避掉了。

HttpClientFactory优势:

HttpClientFactory 是ASP.NET CORE 2.1中新添加加的功能。

  • “完美”处理了我多年来遇到的这些坑,可以更加专注于业务代码。
  • HttpClientFacotry很高效,可以最大程度上节省系统socket。(“JUST USE IT AND FXXK SHUT UP”:P)
  • Factory,顾名思义HttpClientFactory就是HttpClient的工厂,内部已经帮我们解决好了对HttpClient的管理,不需要我们人工进行对象释放,同时,支持自己设置请求头,支持DNS升级等等等。

从微软源码分析,HttpClient继承自HttpMessageInvoker,而HttpMessageInvoker实质就是HttpClientHandler。

HttpClientFactory 创立的HttpClient,也即是HttpClientHandler,只是这些个HttpClient被放到了“池子”中,工厂每次在create的时候会自动判断是新建还是复用。(默认生命周期为2min)

还了解不了的话,可以参考Task和Thread的关系,以前碰到HttpClient这个问题的时候,就一直在想微软什么时候官方出一个HttpClient的Factory,尽管时隔了这么多年直到.NET CORE 2.1才出,但也很是兴奋。

HttpClientFactory使用方法:

借助ASP.NET CORE MVC,可以很方便的进行HttpClient的使用

  1. 在Startup.cs中进行注册
  public class Startup    {        public Startup(IConfiguration configuration)        {            Configuration = configuration;        }        public IConfiguration Configuration { get; }        // This method gets called by the runtime. Use this method to add services to the container.        public void ConfigureServices(IServiceCollection services)        {            //other codes                        services.AddHttpClient("client_1",config=>  //这里指定的name=client_1,可以方便我们后期服用该实例            {                config.BaseAddress= new Uri("http://client_1.com");                config.DefaultRequestHeaders.Add("header_1","header_1");            });            services.AddHttpClient("client_2",config=>            {                config.BaseAddress= new Uri("http://client_2.com");                config.DefaultRequestHeaders.Add("header_2","header_2");            });            services.AddHttpClient();            //other codes            services.AddMvc().AddFluentValidation();        }      }
  1. 使用,这里直接以controller为例,其余地方自行DI
    public class TestController : ControllerBase    {        private readonly IHttpClientFactory _httpClient;        public TestController(IHttpClientFactory httpClient)        {            _httpClient = httpClient;        }        public async Task<ActionResult> Test()        {            var client = _httpClient.CreateClient("client_1"); //复用在Startup中定义的client_1的httpclient            var result = await client.GetStringAsync("/page1.html");            var client2 = _httpClient.CreateClient(); //新建一个HttpClient            var result2 = await client.GetStringAsync("http://www.site.com/XXX.html");            return null;        }    }

实战用法2:使用自己设置类执行HttpClientFactory请求

  1. 自己设置HttpClientFactory请求类
public class SampleClient{    public HttpClient Client { get; private set; }        public SampleClient(HttpClient httpClient)    {        httpClient.BaseAddress = new Uri("https://api.SampleClient.com/");        httpClient.DefaultRequestHeaders.Add("Accept", "application/json");        httpClient.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");        Client = httpClient;    }}
  1. 在Startup.cs中ConfigureService方法中注册SampleClient,代码如下,
services.AddHttpClient<SampleClient>();
  1. 调用:
public class ValuesController : Controller{    private readonly SampleClient  _sampleClient;;      public ValuesController(SampleClient  sampleClient)    {        _sampleClient = sampleClient;    }      [HttpGet]    public async Task<ActionResult> Get()    {        string result = await  _sampleClient.client.GetStringAsync("/");        return Ok(result);    }}

实战用法3:完全封装HttpClient可以使用下面方法

  1. 自己设置HttpClientFactory请求类
public interface ISampleClient{    Task<string> GetData();} public class SampleClient : ISampleClient{    private readonly HttpClient _client;     public SampleClient(HttpClient httpClient)    {        httpClient.BaseAddress = new Uri("https://api.SampleClient.com/");        httpClient.DefaultRequestHeaders.Add("Accept", "application/json");        httpClient.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");        _client = httpClient;    }     public async Task<string> GetData()    {        return await _client.GetStringAsync("/");    }}
  1. 在Startup.cs中ConfigureService方法中注册SampleClient,代码如下,
services.AddHttpClient<ISampleClient, SampleClient>();
  1. 调用:
public class ValuesController : Controller{    private readonly ISampleClient  _sampleClient;;         public ValuesController(ISampleClient  sampleClient)    {        _sampleClient = sampleClient;    }         [HttpGet]    public async Task<ActionResult> Get()    {        string result = await _sampleClient.GetData();        return Ok(result);    }}

说明
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » 优雅通过HttpClientFactory使用HttpClient

发表回复