寫在前面
創建HttpClient實例的時候,在內部會創建HttpMessageHandler鏈,我們知道HttpMessageHandler是負責建立連接的抽象處理程序,所以HttpClient的維護實際上就是維護HttpMessageHandler的使用,釋放HttpClient並不會及時釋放連接,而通常情況下一般是創建全局使用的HttpClient實例,以減少重復連接的次數。當然這種方式所帶來的的弊端也是顯而易見的,因為當前的HttpClient實例所指向的服務器發生問題或者DNS發生變更,那么該實例是無法做到自動更新指向的。
以下為運行其流程圖:
HttpClientFactory自.NET Core 2.1引入,可以認為它是一個配置和創建HttpClient的中心化,.NET Core通過引入HttpClientFactory用於自動化維護HttpMessageHandler池及其生命周期,避免在手動管理 HttpClient生存期時出現的常見 DNS 問題。在默認情況下MessageHandler的活躍狀態是兩分鍾,也就是說,在兩分鍾后,就可以為HttpClient實例重新定位到正確的主機上。
本文的討論思路將從我們能看到的代碼開始一步步深入。
詳細介紹
HttpClientFactory的功能主要位於Microsoft.Extensions.Http包中,它已經默認包含在Microsoft.AspNetCore.App元包中。針對HttpClientFactory的處理涉及到IHttpClientBuilder、IHttpClientFactory、IHttpMessageHandlerFactory、ITypedHttpClientFactory這幾大接口,以下將分別做討論。
services.AddHttpClient()
我們在創建或者配置HttpClient對象的時候,會在ConfigureServices
方法中增加services.AddHttpClient(),即可注冊IHttpClientFactory。
這段代碼位於Microsoft.Extensions.DependencyInjection.HttpClientFactoryServiceCollectionExtensions中,它會初始化相關信息並注冊到IServiceCollection中,這些信息包括日志、選項、核心抽象功能、類型客戶端以及其他基礎設施功能。
需要注意的是,在核心抽象功能中,DefaultHttpClientFactory是單例模式的,其所繼承的接口對象的獲取也是單例的,而HttpMessageHandlerBuilder注冊方式確是每一次GetService的時候都會創建一個新的HttpMessageHandlerBuilder實例。
以下為services.AddHttpClient()的源代碼,其中標紅部分為核心抽象功能的注冊:
1: public static IServiceCollection AddHttpClient(this IServiceCollection services)
2: {
3: if (services == null)
4: {
5: throw new ArgumentNullException(nameof(services));
6: }
7:
8: services.AddLogging();
9: services.AddOptions();
10:
11: services.TryAddTransient<HttpMessageHandlerBuilder, DefaultHttpMessageHandlerBuilder>();
12: services.AddSingleton<DefaultHttpClientFactory>();
13: services.TryAddSingleton<IHttpClientFactory>(serviceProvider => serviceProvider.GetRequiredService<DefaultHttpClientFactory>());
14: services.TryAddSingleton<IHttpMessageHandlerFactory>(serviceProvider => serviceProvider.GetRequiredService<DefaultHttpClientFactory>());
15:
16: services.TryAdd(ServiceDescriptor.Transient(typeof(ITypedHttpClientFactory<>), typeof(DefaultTypedHttpClientFactory<>)));
17: services.TryAdd(ServiceDescriptor.Transient(typeof(DefaultTypedHttpClientFactory<>.Cache), typeof(DefaultTypedHttpClientFactory<>.Cache)));
18:
19: services.TryAddEnumerable(ServiceDescriptor.Singleton<IHttpMessageHandlerBuilderFilter, LoggingHttpMessageHandlerBuilderFilter>());
20:
21: return services;
22: }
DefaultHttpClientFactory
DefaultHttpClientFactory是一個用internal修飾的類,意味着該類只能在在其內部使用。它繼承了IHttpClientFactory、IHttpMessageHandlerFactory這兩個接口。由此可見,DefaultHttpClientFactory實例的創建被拆成了兩種行為。
IHttpClientFactory的定位是一個抽象工廠,可以為指定名稱的HttpClient實例創建自定義配置,它只有一個方法,HttpClient CreateClient(string name)。
IHttpMessageHandlerFactory的定位也是一個抽象工廠,它為指定名稱的HttpMessageHandler實例創建自定義配置,它只有一個方法,HttpMessageHandler CreateHandler(string name)。
我們先看一下這兩個方法的實現,會覺得很有意思
1: public HttpClient CreateClient(string name)
2: {
3: if (name == null)
4: {
5: throw new ArgumentNullException(nameof(name));
6: }
7:
8: var handler = CreateHandler(name);
9: var client = new HttpClient(handler, disposeHandler: false);
10:
11: var options = _optionsMonitor.Get(name);
12: for (var i = 0; i < options.HttpClientActions.Count; i++)
13: {
14: options.HttpClientActions[i](client);
15: }
16:
17: return client;
18: }
19:
20: public HttpMessageHandler CreateHandler(string name)
21: {
22: if (name == null)
23: {
24: throw new ArgumentNullException(nameof(name));
25: }
26:
27: var entry = _activeHandlers.GetOrAdd(name, _entryFactory).Value;
28:
29: StartHandlerEntryTimer(entry);
30:
31: return entry.Handler;
32: }
可以看到,我們通過名稱查找HttpClient對象的時候,也會依照該名稱以GetOrAdd方式去查找相應的HttpMessageHandler對象,也就說HttpClient對象和HttpMessageHandler對象可以通過名稱關聯起來。
需要注意的時候在調用CreateHandler方法的時候會調用StartHandlerEntryTimer方法,這個方法是干嘛的呢,他維護着定時器。該方法位於Microsoft.Extensions.Http.ActiveHandlerTrackingEntry中,我們將此類視為是一個不可變的(當然,其內部的定時器是變化的),為“到期”池創建一個可以顯著簡化線程需求的新對象。
除了這兩個方法外,我們要需要注意DefaultHttpClientFactory對HttpMessageHandler的管理功能。DefaultHttpClientFactory內部維護者一個定時器和兩個HttpMessageHandler對象集合,這兩個集合分別是ActiveHandler和ExpiredHandler。內部定時器會定期從ExpiredHandler集合中掃描並清理無效的 HttpMessageHandler對象。
ActiveHandler集合的增加是在調用CreateHandler方法時增加的,其移除是在回調的時候移除,這個移除入口也只有這一處。
ExpiredHandler集合的增加也是在調用CreateHandler方法時,通過內部的一個回調機制增加的,其移除通過定時器定期掃描來實現的。這處需要注意的是,ExpiredHandlerTrackingEntry這個類中有一個屬性,代碼如下:
1: private readonly WeakReference _livenessTracker;
1: public bool CanDispose => !_livenessTracker.IsAlive;
通過WeakReference 類型的變量來標識該HttpMessageHandler對象是否應該被從集合中移除。
定時器一般是個比較消耗資源,而且一旦用不好,就會引發線程問題,DefaultHttpClientFactory在處理定時器的時候,首先通過停止所有掛起的計時器,在清除后如果還需要繼續處理無效HttpMessageHandler對象,將會重新啟動計時器,雖然看似多余了點,但是比通過鎖定整個清理機制來確定是否阻塞清理任何並啟動定時器要好多了。
1: internal void CleanupTimer_Tick()
2: {
3: StopCleanupTimer();
4:
5: if (!Monitor.TryEnter(_cleanupActiveLock))
6: {
7: StartCleanupTimer();
8: return;
9: }
10:
11: try
12: {
13: var initialCount = _expiredHandlers.Count;
14: Log.CleanupCycleStart(_logger, initialCount);
17:
18: var disposedCount = 0;
19: //開始清理
20:
21: Log.CleanupCycleEnd(_logger, stopwatch.GetElapsedTime(), disposedCount, _expiredHandlers.Count);
22: }
23: finally
24: {
25: Monitor.Exit(_cleanupActiveLock);
26: }
27:
28: if (_expiredHandlers.Count > 0)
29: {
30: StartCleanupTimer();
31: }
32: }
以下為這兩個隊列的處理示意圖: