.NET Core 2.1中的HttpClientFactory最佳實務
ASP.NET Core 2.1中出現一個新的HttpClientFactory功能,
它有助於解決開發人員在使用HttpClient執行個體從其應用程式發出外部Web請求時可能遇到的一些常見問題。
介紹
在.NETCore平台的2.1新增了HttpClientFactory,雖然HttpClient這個類實現了disposable,但使用它的時候用聲明using封裝塊的方式通常不是最好的選擇。處理HttpClient,底層socket通訊端不會立即釋放。該HttpClient類是專為多個請求重複使用而建立的。需要不同的基地址,不同的HTTP標題和其他對請求個人化操作的情境時,需要手動管理多個HttpClient執行個體,為了簡化HttpClient執行個體管理,.NET Core 2.1提供了一個新的HTTPClientFactory - 它可以建立,緩衝和處理HttpClient執行個體。
什麼是HttpClientFactory?
用ASP.NET團隊的話說:“an opinionated factory for creating HttpClient instances”(一個用於建立HttpClient執行個體的最佳實務的工廠),並且是ASP.NET Core 2.1發布的新功能。根據大家以前使用HttpClient的經驗,您可能遇到一些困擾的問題,有時甚至沒有意識到您有問題(只是在並發並不大的情境沒觸發而已)。
第一個問題是當你在代碼中建立太多的HttpClients時,這反過來會產生兩個問題......
- 這是低效的,因為每個請求都有自己的遠程伺服器串連池。這意味著您需要為每個建立的用戶端支付重新串連到該遠程伺服器的成本。
- 更大的問題是如果你建立了很多HttpClient並使用到他們,你可以遇到Socket耗盡,而你基本上已經太快地使用了過多的Socket。您可以同時開啟多個Socket是有限制的。當您dispose銷毀HttpClient時,它開啟的串連在TIME_WAIT狀態下保持開啟狀態最長240秒(如果來自遠程伺服器的任何資料包仍然通過)。
HttpClient實現了IDisposable,這通常會導致開發人員在使用IDisposable對象時遵循正常模式,在using塊中建立它。這樣可以確保一旦完成對象並且它已經超出範圍,就可以正確銷毀對象。
因此,最優的方法是重用HttpClient執行個體,以便也可以重用串連。HttpClient是一個可變對象,但只要你沒有運行期改變它,它實際上是安全執行緒的並且可以共用。因此,一種常見的方法是將其註冊為具有DI架構的單例模式,或者建立包含static靜態執行個體的對象。
但是,這會產生新問題。以這種方式使用單個HttpClient將保持串連開啟並且不遵守DNS存留時間(TTL)設定(總之就是同一個HttpClient執行個體只能有一個要求標頭,在被請求方發生更改時,由於是單例不能做個人化改變,否則導致其他請求失敗)。現在串連將永遠不會獲得DNS更新,因此您正在與之通訊的伺服器將永遠不會更新其地址。在某些情況下,這是完全有可能的,在以上這種情況下,您可以平衡許多主機,這些主機可能隨著時間的推移而改變,或者可能使用Blue/Green 部署推出新服務。如果伺服器消改變,則您的串連使用的IP可能不再響應您通過單個HttpClient發出的請求。
所以需要我們手動去管理每類伺服器的HttpClient的執行個體來進行個人化要求標頭的構造和發起請求!
HttpClientFactory旨在協助您開始解決這些問題,並提供了一種新的機制來建立在幕後為我們正確管理的HttpClient執行個體。它將為我們“做管理HttpClient的事”,我們可以專註於業務!雖然在參考HttpClient時提到了上述問題,但事實上問題的根源實際上發生在HttpClient上,HttpClient使用了HttpClientHandler。HttpClientFactory管理處理常式的生命週期,以便我們有一個可以重用的池,同時還可以(Rotating)輪換它們以使DNS不會過時。
使用HttpClient的昂貴部分實際上是建立HttpClientHandler和串連。以這種HttpClientFacotry方式彙集這些內容意味著我們可以更高效利用資源最節省地使用我們系統上的socket。當您使用HttpClientFactory請求HttpClient時,實際上每次都會獲得一個新執行個體,這意味著我們不必擔心會改變它的狀態。此HttpClient可能(或可能不)使用池中的現有HttpClientHandler,從而使用現有開啟的串連。
預設情況下,每個新建立的HttpClientHandler(派生自HttpMessageHandler)生命週期只有2分鐘。通過services.AddHttpClient()建立HttpClientFactory執行個體時,可以根據每一個命名的Client客戶機進行控制。達到生命週期後,處理常式將不會立即被釋放掉,而是放入到期的池中。任何依賴於HttpClientFactory的處理常式鏈的用戶端都可以繼續使用它而沒有任何問題。有一個後台作業檢查到期的池,以查看處理常式的所有引用是否已在scope之外,此時可以將其釋放掉。處理常式鏈到期後對新用戶端的任何新請求都將獲得新的處理常式鏈。
這種方法運行得相當不錯,但.NET Core方面還有其他一些事情可能會進一步改善這種情況。.NET Core團隊開發了一個新的ManagedHandler,它可以更正確地管理DNS,原則上可以保持更長時間,這意味著可以更有效地共用串連。這個新的處理常式還被設計為在不同的作業系統中更加一致地運行。在該工作完成之前,上面的處理常式池是一個合理的解決方案。
如何使用HttpClientFactory
我們將首先建立一個簡單的WebAPI項目
接下來,我們需要轉到我們的Startup.cs檔案並註冊一個服務。
services.AddHttpClient();
services.AddScoped(typeof(ClassInService));//此處無關HttpClient,請暫時忽視他
在幕後,這將註冊一些必需的服務,其中一個是IHttpClientFactory的實現。接下來,我們在業務中使用他
public class ClassInService { /// <summary> /// 構建器 /// </summary> /// <param name="clientFactory"></param> public ClassInService(IHttpClientFactory clientFactory) { _clientFactory = clientFactory; }}
private void HttpClientFactoryTest() { var client = _clientFactory.CreateClient("這是專門用來串連部落格園的");//必須和services.AddHttpClient()中指定的名稱對應 var content = new StringContent($"SID={SID}&safeKey={111}"); content.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded"); var response = client.PostAsync("MyBlogUrl", content); }
這裡我們首先添加對IHttpClientFactory的依賴,它將由DI系統注入ClassInService。IHttpClientFactory允許我們請求和接收HttpClient執行個體。
我們使用HttpClientFactory建立用戶端。在幕後,HttpClientFactory將為我們建立一個新的HttpClient。但是等等,之前說過為每個請求使用新的HttpClient是很糟糕。但此處的建立的httpclient是在他所管理的池子中,並不每個請求都會是新的socket。
HttpClientFactory收集這些HttpClientHandler執行個體並管理它們的生命週期,以解決之前提到的一些問題。每次我們要求HttpClient時,我們都會得到一個新執行個體,它可能(或可能不)使用現有的HttpClientHandler。HttpClient本身並沒有問題。
一旦建立,由此建立的所有HttpClientHandler將被預設保持約2分鐘。這意味著針對同一個CreateClient的任何新請求都可以共用處理常式,因此也可以共用串連。當HttpClient存在時,它的處理常式將保持可用狀態,並且它將再次共用串連。
兩分鐘後,每個HttpClientHandler都標記為已到期。到期狀態只是標記它們,以便在建立任何新的HttpClient執行個體時不再使用它們。但是,它們不會立即銷毀,因為其他HttpClient執行個體可能正在使用它們。HttpClientFactory使用後台服務監視到期的處理常式,一旦它們不再被引用,就可以正確釋放它們,也允許它們的串連被關閉。
概要
通過使用HttpClientfactory我們不需要考慮如何管理HttpClient的生命週期或擔心遇到DNS問題。以上只是HttpClient小小的最佳使用推薦,還有其他進階用法,例如和Polly的結合使用。
參考: