原文连接:blog.zhuliang.ltd/back-end/co…html
从Task起,终于在.NET CORE 2.1 等到 HttpClient 的Pool。。。web
在C#中,平时咱们在使用HttpClient的时候,会将HttpClient包裹在using内部进行声明和初始化,如:windows
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链接状况:框架
默认在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的童鞋可能更加记忆尤新,问题追根溯源都是换汤不换药。)async
熊厂里面可以搜索出来的解决方法,基本都是“减小超时时间”,但人为减小了超时时间会出现各类莫名其妙的错误。且没法避免服务器早晚崩溃的问题。
能够经过注册表进行修改默认值:[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的这些个问题?直到我遇到了 HttpClientFactory,瞬间写代码幸福感倍升,也感慨新时代的童鞋们真的太幸福了,老一辈踩的坑能够“完美”规避掉了。
HttpClientFactory 是ASP.NET CORE 2.1中新增长的功能。
从微软源码分析,HttpClient继承自HttpMessageInvoker,而HttpMessageInvoker实质就是HttpClientHandler。
HttpClientFactory 建立的HttpClient,也便是HttpClientHandler,只是这些个HttpClient被放到了“池子”中,工厂每次在create的时候会自动判断是新建仍是复用。(默认生命周期为2min)
还理解不了的话,能够参考Task和Thread的关系,之前碰到HttpClient这个问题的时候,就一直在想微软何时官方出一个HttpClient的Factory,虽然时隔了这么多年直到.NET CORE 2.1才出,但也非常兴奋。
借助ASP.NET CORE MVC,能够很方便的进行HttpClient的使用
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();
}
}
复制代码
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;
}
}
复制代码