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


引言:

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

  (1)在處理HttpClient對象的時候不會立即關閉socket。

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

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

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

 

一、HttpClient存在的問題

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

  為了理解我們為什么遇到這種情況, 我們首先要看另一個面向連接的類: SqlConnection。 這個類實現了IDisposable接口,所以絕大多數開發人員都是這樣寫的,實例如下:

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

雖然,這個例子在解釋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就比較特殊,這不怪我們,文檔就是錯誤的。

 

二、HttpClientFactory in ASP.NET Core 2.1就解決了上面所有的問題

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

 

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

 

三、總結

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

 

 

 

參考文章:

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

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

 

作者:郭崢

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

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM