原文連接: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;
}
}
複製代碼