ASP.Net Core2.1中的HttpClientFactory系列一:HttpClient的缺陷

引言:html

  ASP.NET Core2.1 中出現了一個新的 HttpClientFactory 功能, 它有助於解決開發人員在使用 HttpClient 實例從其應用程序中訪問外部 web 資源時可能遇到的一些常見問題。關於HttpClientFactory 到底解決了那些HttpClient的嚴重問題,下面是我羅列出來的(原文來自於:https://www.infoq.com/news/2016/09/HttpClient)web

  (1)在處理HttpClient對象的時候不會當即關閉socket。數據庫

  (2)太多的實例影響性能安全

  (3)單例的HttpClient或者共享HttpClient實例,不遵照DNS 生存時間 (TTL) 設置。(這個問題我也不太明白,具體怎麼重現這個問題,我下去再研究研究。)服務器

HttpClientFactory這個小可愛,解決了上面的全部問題,他也是ASP.NET Core2.1最新特色之一,下面詳細聊聊HttpClient存在的這些問題。網絡

 

1、HttpClient存在的問題

  因爲設計錯誤、bug 和文檔不正確等因素, 致使在.Net中正確使用HttpClient 出奇的難。所以, 在生產環境中看起來正常工做的應用程序可能會在負載大的狀況下產生性能和運行時故障的問題。socket

  爲了理解咱們爲何遇到這種狀況, 咱們首先要看另外一個面向鏈接的類: SqlConnection。 這個類實現了IDisposable接口,因此絕大多數開發人員都是這樣寫的,實例以下:async

  using (var con = new SqlConnection(connectionString)) {
    
con.open();
    
//use the connection here
  } //this closes the connection  tcp

雖然,這個例子在解釋HttpClient存在的問題不是很到位,可是使用這種方式,來寫上面的代碼,是沒錯的。若是你嘗試這把這種模式應用到實現了IDisposable接口的HttpClient,則會遇到一些很奇怪的問題。具體的說,它會打開比實際須要更多的socket,加劇了服務器的負載。此外使用using語句是不會關閉這些套接字的,相反,在應用程序中止使用它們時,會關閉幾分鐘。工具

 

Connection Pooling

回到 SqlConnection 示例中, 大多數面向鏈接的資源都是有鏈接池的。當您 "打開 " 數據庫鏈接時, 它首先檢查池中是否有可用的、未使用的鏈接。若是它找到一個, 將重用它, 而不是建立一個新的鏈接。

一樣, 當您 "關閉 " SqlConnection 它只是將鏈接釋放到連接池中。最終, 一個單獨的進程可能會關閉長時間未使用的鏈接。

HttpClient 不這樣作。當您處理它時, 它將啓動關閉它所控制的套接字的過程。這意味着下次有請求時, 您必須通過一個全新的鏈接週期。若是您的網絡有很高的延遲或您的鏈接是安全的, 則這會特別痛苦, 由於後者須要新一輪的 SSL/TLS 協商。

 

Closing a Socket Takes Four Minutes

 

如上所述,關閉套接字不是一個快速的過程。 當你「關閉」套接字時,你真正在作的是將它置於TIME_WAIT狀態。 Windows將在此狀態下保持鏈接240秒,以防萬一剩餘的數據包仍在傳輸中。

這使您更有可能耗盡可用套接字的數量,從而致使運行時錯誤,例如「沒法鏈接到遠程服務器.System.Net.Sockets.SocketException:每一個套接字地址只有一種用法(協議/ 網絡地址/端口一般是容許的「。

下面咱們來實踐一下,看看真相:

 

示例演示:(注意使用的是.Net Core 1.0)

using System; using System.Net.Http; using System.Threading.Tasks; namespace ConsoleApp2 { class Program { static async Task Main(string[] args) { Console.WriteLine("Starting connections"); for (int i = 0; i < 10; i++) { using (var client = new HttpClient()) { var result = await client.GetAsync("http://aspnetmonsters.com/");
            Console.WriteLine(result.StatusCode);
          }
       }
      Console.WriteLine(
"Connections done");
      Console.ReadKey();
    }
  }
}

 這將會打開10個請求,並以get的方式,去請求博客園,咱們只打印出狀態碼。

輸出結果:

到這個地步,可能咱們就會很高興,搞定!閃人!,真的搞定了嗎?個人小可愛,下面咱們使用netstat 工具並查看運行它的機器上的套接字狀態,咱們將看到:

看到沒,個人應用程序已經執行完了,可是,仍然有不少連接在打開上面圖示中的主機,它們都處於TIME_WAIT狀態,這意味這咱們在應用程序這邊已經把連接關閉了,可是咱們仍然在等待查看,是否有額外的數據包進來,使用這些連接。由於它們可能在網絡的某個地方已經被延遲,下面咱們來看一張TCT/IP圖,該圖引自(https://www4.cs.fau.de/Projects/JX/Projects/TCP/tcpstate.html)

 

Windows將在此狀態下保持鏈接240秒(由[HKEY_LOCAL_MACHINE \ SYSTEM \ CurrentControlSet \ Services \ Tcpip \ Parameters \ TcpTimedWaitDelay]設置)。 Windows能夠快速打開新套接字的速度有限,所以若是您耗盡鏈接池,那麼您可能會看到以下錯誤:

Unable to connect to the remote server
System.Net.Sockets.SocketException: Only one usage of each socket address (protocol/network address/port) is normally permitted.

在谷歌搜索會給你一些關於減小鏈接超時的可怕建議。 事實上,當在服務器上運行着正確使用HttpClient或相似構造的應用程序時,減小超時可能會致使其餘不利後果。 咱們須要瞭解「正確」意味着什麼,並去修復底層的問題而不是去修補機器級的變量。

若是咱們共享一個HttpClient實例,那麼咱們能夠經過重用它們來減小套接字的浪費:

請注意,咱們只爲整個應用程序共享了一個HttpClient實例。 仍然能夠正常工做(實際上因爲套接字重用而快一點)。 Netstat如今只顯示:

 

好了,總結一下:在.Net Core 1.0以前的版本,使用的時候須要注意下面的兩點:

(1)確保你的HttpClient 是 static

(2)不要丟棄或包裝HttpClient 在一個using塊中。

小弟我才疏學淺,有不對的地方能夠指出來,共同探討。其實,咱們一直使用using把它包起來,也是沒錯的,是由於HttpClient實現了IDisposable ,可是HttpClient就比較特殊,這不怪咱們,文檔就是錯誤的。

 

2、HttpClientFactory in ASP.NET Core 2.1就解決了上面全部的問題

HttpClientFactory 這個小可愛,就解決了上面的全部問題,她也是ASP.NET Core 2.1中最新特色之一,有了她咱們就不用關心如何建立HttpClient,又如何釋放它。關於如何使用它,博客園中的有介紹的,我這裏就再也不講述了。

 

具體請參考:https://www.cnblogs.com/willick/p/9640589.html

 

3、總結

到這裏該系列文章的第一篇就講完了,有不對的地方,還請大佬們能指出來,共同探討,好了,但願對你有所幫助,該系列文章,每週末更新,愛看不看,哈哈哈~~~~~~

 

 

 

參考文章:

(翻譯)https://www.infoq.com/news/2016/09/HttpClient

(翻譯)https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/

 

做者:郭崢

出處:http://www.cnblogs.com/runningsmallguo/

本文版權歸做者和博客園共有,歡迎轉載,但未經做者贊成必須保留此段聲明,且在文章頁面明顯位置給出原文連接。

相關文章
相關標籤/搜索