优雅通过HttpClientFactory使用HttpClient
本文主要讲解如何优雅的在 .NET CORE 中使用 HttpClient ,对比了下 Framework 和 Core 中的 Http 客户端。
提供了几个封装 HttpClient 的思路和经验。
看完此文,基本可以自行封装一个客户端
HttpClient 日常使用及坑点:
在C#中,平时我们在使用 HttpClient 的时候,会将 HttpClient 包裹在 using 内部进行声明和初始化,如:
1 | using(var httpClient = new HttpClient()) |
至于为什么?无外乎是:项目代码中就是这样写的,依葫芦画瓢/别人就是这样用的/在微软官方的 ASP.NET 教程中也是这么干的。
说的技术范点:当你使用继承了 IDisposable 接口的对象时,建议在 using 代码块中声明和初始化,当 using 代码段执行完成后,会自动释放该对象而不需要手动进行显示 Dispose 操作。
但这里, HttpClient 这个对象有点特殊,虽然继承了 IDisposable 接口,但它是可以被共享的(或者说可以被复用),且线程安全。从项目经验来看,倒是建议在整个应用的生命周期内,复用 HttpClient 实例,而不是每次RPC请求的时候就实例化一个。(之前在优化公司一个web项目的时候,也曾经因为HttpClient载过一次坑,后面我会进行简述。)
我们先来用个简单的例子做下测试,看为什么不要每次RPC请求都实例化一个 HttpClient :
1 | public class Program |
运行项目输出结果后,通过 netstate 查看下 TCP 连接情况:
-
虽然项目已经运行结束,但是连接依然存在,状态为 " TIME_WAIT" (继续等待看是否还有延迟的包会传输过来。)。
默认在 windows 下, TIME_WAIT 状态将会使系统将会保持该连接 240s 。
-
这里也就引出了我上面说的载过的一次坑:在高并发的情况下,连接来不及释放, socket 被耗尽,耗尽之后就会出现喜闻乐见的一个错误:
1 | #使用 jemter 压测复现错误信息: |
说白话:就是会出现 “各种套接字问题” 。(码 WCF 的童鞋可能更加记忆尤新,问题追根溯源都是换汤不换药。)
熊厂里面能够搜索出来的解决方法,基本都是 “减少超时时间” ,但人为减少了超时时间会出现各种莫名其妙的错误。且无法避免服务器迟早崩溃的问题。
可以通过注册表进行修改默认值:[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\TcpTimedWaitDelay])
那么如何处理这个问题?答案已经在上面说了, “复用HttpClient” 即可。如:
1 | public class Program |
-
可以看到,原先 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”😛)
-
Factory,顾名思义 HttpClientFactory 就是 HttpClient 的工厂,内部已经帮我们处理好了对 HttpClient 的管理,不需要我们人工进行对象释放,同时,支持自定义请求头,支持DNS更新等等等。
从微软源码分析,HttpClient 继承自 HttpMessageInvoker ,而 HttpMessageInvoker 实质就是 HttpClientHandler 。
HttpClientFactory 创建的 HttpClient ,也即是 HttpClientHandler ,只是这些个 HttpClient 被放到了“池子”中,工厂每次在 create 的时候会自动判断是新建还是复用。(默认生命周期为 2 min)
还理解不了的话,可以参考 Task 和 Thread 的关系,以前碰到 HttpClient 这个问题的时候,就一直在想微软什么时候官方出一个 HttpClient 的 Factory ,虽然时隔了这么多年直到.NET CORE 2.1 才出,但也很是兴奋。
HttpClientFactory使用方法:
借助 ASP.NET CORE MVC ,可以很方便的进行 HttpClient 的使用
-
在 Startup.cs 中进行注册
1 | public class Startup |
-
使用,这里直接以 controller 为例,其他地方自行 DI
1 | public class TestController : ControllerBase |
实战用法2:使用自定义类执行 HttpClientFactory 请求
-
自定义 HttpClientFactory 请求类
1 | public class SampleClient |
-
在 Startup.cs 中 ConfigureService 方法中注册 SampleClient,代码如下,
1 | services.AddHttpClient<SampleClient>(); |
-
调用:
1 | public class ValuesController : Controller |
实战用法3:完全封装 HttpClient 可以使用下面方法
-
自定义 HttpClientFactory 请求类
1 | public interface ISampleClient |
-
在Startup.cs中ConfigureService方法中注册SampleClient,代码如下,
1 | services.AddHttpClient<ISampleClient, SampleClient>(); |
-
调用:
1 | public class ValuesController : Controller |
FAQ
SSL connection could not be established
解决方法:在注册 HttpClient 的时候,指定下 HttpClientHandler 忽略证书
1 | services.AddHttpClient<IXXXClient, XXXClient>().ConfigureHttpMessageHandlerBuilder(builder => |
另外需要注意下:你在本地调用的时候没有问题,但是一旦发布到服务器后,就各种 SSL 错误,请求超时等问题,此时你需要确认下请求的服务端是否有生成证书。
-
比较好的情况是:通过 openssl 等证书生成工具,在服务器端生成 CA 文件,客户端自动下载这些 CA 文件,解决掉证书问题导致的 SSL 报错。
-
如果无法申请证书的,则建议还是走 http 协议的好,可以 http 和 https 都开启(但这里需要注意下,不要访问 http 的时候,全部跳转到 https 了)
- 可以排查是否有在代码中使用指定 app.UseHttpsRedirection()
- 排查 IIS 中是否有设置 URL Rewrite
- 排查负载均衡中是否有相关选项