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

using(var httpClient = new HttpClient()){    }

至于为什么?无外乎是:项目代码中就是这样写的,依葫芦画瓢/别人就是这样用的/在微软官方的 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 连接情况:

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

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

  • 这里也就引出了我上面说的载过的一次坑:在高并发的情况下,连接来不及释放,socket 被耗尽,耗尽之后就会出现喜闻乐见的一个错误:

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

  • 可以看到,原先 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 才出,但也很是兴奋。

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

实战用法1:常规用法

统一在发布项目中声明和配置。

1. 在 Startup.cs 中进行注册

  public class Startup    {        public Startup(IConfiguration configuration)        {            Configuration = configuration;        }        public IConfiguration Configuration { get; }                public void ConfigureServices(IServiceCollection services)        {                                    services.AddHttpClient("client_1",config=>            {                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();                        services.AddMvc().AddFluentValidation();        }      }

2. 使用,这里直接以 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");            var result = await client.GetStringAsync("/page1.html");            var client2 = _httpClient.CreateClient();            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;    }}

2. 在 Startup.cs 中 ConfigureService 方法中注册 SampleClient,代码如下,

services.AddHttpClient<SampleClient>();

3. 调用:

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("/");    }}

2. 在 Startup.cs 中 ConfigureService 方法中注册 SampleClient,代码如下,

services.AddHttpClient<ISampleClient, SampleClient>();

3. 调用:

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

HttpClientFactory 使用说明 及 对 HttpClient 的回顾和对比相关推荐

  1. 这次使用一个最舒服的姿势插入HttpClient拦截器技能点

    码甲哥继续在同程艺龙写一点大前端,今天我们来了解一下如何拦截axios请求/响应?这次我们举一反三,用一个最舒适的姿势插入这个技能点. 本文阅读耗时5 minute,行文耗时5 Days. axios ...

  2. HttpClient在.NET Core中的正确打开方式

    问题来源 长期以来,.NET开发者都通过下面的方式发送http请求: using (var httpClient = new HttpClient()) {     var response = aw ...

  3. HttpClient参观记:.net core 2.2 对HttpClient到底做了什么?

    .net core 于 10月17日发布了 ASP.NET Core 2.2.0 -preview3,在这个版本中,我看到了一个很让我惊喜的新特性:HTTP Client Performance Im ...

  4. HttpClientFactory系列二:集成Polly处理瞬态故障

    前言:最近,同事在工作中遇到了使用HttpClient,有些请求超时的问题,辅导员让我下去调研一下,HttpClinet的使用方式已经改成了之前博客中提到的方式,问题的原因我已经找到了,就是因为使用了 ...

  5. 【ASP.NET Web API教程】3.4 HttpClient消息处理器

    [ASP.NET Web API教程]3.4 HttpClient消息处理器 原文:[ASP.NET Web API教程]3.4 HttpClient消息处理器 注:本文是[ASP.NET Web A ...

  6. 17:Polly与HttpClientFactory

    翻译自:https://github.com/App-vNext/Polly/wiki/Polly-and-HttpClientFactory 点此跳转到系列目录 从ASPNET Core 2.1开始 ...

  7. 在.NET 6 中如何创建和使用 HTTP 客户端 SDK

    如今,基于云.微服务或物联网的应用程序通常依赖于通过网络与其他系统通信.每个服务都在自己的进程中运行,并解决一组有限的问题.服务之间的通信是基于一种轻量级的机制,通常是一个 HTTP 资源 API. ...

  8. 挑战独立开发项目能力___ITlanbao

    2019独角兽企业重金招聘Python工程师标准>>> 做了5年的android开发,今天没事写写刚入行不久的时候第一次独立开发项目的心得体会,     当时我刚工作8个月,由于公司 ...

  9. 前端逻辑练习题+学习记录 不定期更新

    编程题 1.返回数组中最大的数字(reduce函数) largestOfFour([[4, 5, 1, 3], [13, 27, 18, 26], [32, 35, 37, 39], [1000, 1 ...

最新文章

  1. Jboot 2.0.1 发布,新增基于 Fescar 的分布式事务支持
  2. 百度推出飓风算法,严厉打击恶劣采集
  3. 如何实现拼音与汉字的互相转换
  4. centos中如何找出系统中 load 高时处于运行队列的进程
  5. wxWidgets:wxClipboard类用法
  6. JAVA NIO是什么(zz)
  7. 公有云:美酒or毒药?--【软件和信息服务】2014.12
  8. JavaScript每日学习日记(0)
  9. 虚拟主机中,不修改IIS设置,在IIS6下运行MVC架构的网站
  10. Ubantu install jdk
  11. java commons logging_Java日志介绍(5)-commons-logging
  12. python计算机中丢失api-ms-win-crt-runtime-l_api-ms-win-crt-runtime-l1-1-0.dll
  13. 关于TortoiseGit汉化包装了,但仍然是英文菜单的问题记录
  14. sublime 添加 ConvertToUTF-8
  15. 非计软专业的学生也能看懂的面向对象编程(《面向对象编程是怎样工作的》平野章/著 读书笔记)
  16. Windows 文件系统格式 Raw格式转换NTFS
  17. 睡前必备神器——倒计时定时关闭手机音乐声音APP
  18. css中设置body字体,css如何设置body字体颜色
  19. 中国无花果市场供需形势与未来产值预测报告(新版)2022-2027年
  20. java 1st 2nd 3rd 4th_1st(3rd)

热门文章

  1. 世界杯,世界共同的游戏!
  2. 《蛋白质女孩》的一句台词
  3. Microsoft Teams:团队Owner离开公司后,我们该怎么做?
  4. 大学留级两年不敢和家人说_您说什么:如何与家人保持联系?
  5. 如何将联系人导入Windows联系人文件夹和从中导出联系人
  6. Windows下压缩包安装Mysql
  7. 解决在Python中使用Win32api报错的问题,No module named win32api
  8. Oracle即将发布的全新Java垃圾收集器 ZGC
  9. 「每天一道面试题」如何理解方法的重载与覆盖?
  10. QQ旋风爆缓冲区溢出漏洞