写在前面
创建 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:
- }
以下为这两个队列的处理示意图:
来源: https://www.cnblogs.com/edison0621/p/11224890.html