写在前面

创建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:  }

以下为这两个队列的处理示意图:

.NET Core 3.0之深入源码理解HttpClientFactory(一)的更多相关文章

  1. .NET Core 3.0之深入源码理解HttpClientFactory(二)

      写在前面 上一篇文章讨论了通过在ConfigureServices中调用services.AddHttpClient()方法,并基于此进一步探讨了DefaultHttpClientFactory是 ...

  2. .NET Core 3.0之深入源码理解Startup的注册及运行

    原文:.NET Core 3.0之深入源码理解Startup的注册及运行   写在前面 开发.NET Core应用,直接映入眼帘的就是Startup类和Program类,它们是.NET Core应用程 ...

  3. .NET Core 3.0之深入源码理解Configuration(一)

    Configuration总体介绍 微软在.NET Core里设计出了全新的配置体系,并以非常灵活.可扩展的方式实现.从其源码来看,其运行机制大致是,根据其Source,创建一个Builder实例,并 ...

  4. .NET Core 3.0之深入源码理解Kestrel的集成与应用(一)

      写在前面 ASP.NET Core 的 Web 服务器默认采用Kestrel,这是一个基于libuv(一个跨平台的基于Node.js异步I/O库)的跨平台.轻量级的Web服务器. 在开始之前,先回 ...

  5. .NET Core 3.0之深入源码理解Kestrel的集成与应用(二)

      前言 前一篇文章主要介绍了.NET Core继承Kestrel的目的.运行方式以及相关的使用,接下来将进一步从源码角度探讨.NET Core 3.0中关于Kestrel的其他内容,该部分内容,我们 ...

  6. .NET Core 3.0之深入源码理解Host(二)

      写在前面 停了近一个月的技术博客,随着正式脱离996的魔窟,接下来也正式恢复了.本文从源码角度进一步讨论.NET Core 3.0 中关于Host扩展的一些技术点,主要讨论Long Run Pro ...

  7. .NET Core 3.0之深入源码理解ObjectPool(一)

    写在前面 对象池是一种比较常用的提高系统性能的软件设计模式,它维护了一系列相关对象列表的容器对象,这些对象可以随时重复使用,对象池节省了频繁创建对象的开销. 它使用取用/归还的操作模式,并重复执行这些 ...

  8. .NET Core 3.0之深入源码理解HealthCheck(一)

    写在前面 我们的系统可能因为正在部署.服务异常终止或者其他问题导致系统处于非健康状态,这个时候我们需要知道系统的健康状况,而健康检查可以帮助我们快速确定系统是否处于正常状态.一般情况下,我们会提供公开 ...

  9. .NET Core 3.0之深入源码理解Host(一)

    写在前面 ASP .NET Core中的通用主机构建器是在v2.1中引入的,应用在启动时构建主机,主机作为一个对象用于封装应用资源以及应用程序启动和生存期管理.其主要功能包括配置初始化(包括加载配置以 ...

随机推荐

  1. ECSHOP 数据库结构说明

    ECSHOP 数据库结构说明 (适用版本v2.7.3) 1.account_log 用户账目日志表 字段 类型 Null/默认 注释 log_id mediumint(8) 否 / 自增 ID 号 u ...

  2. 毕设(五)ListView

    ListView 控件可使用四种不同视图显示项目.通过此控件,可将项目组成带有或不带有列标头的列,并显示伴随的图标和文本. 可使用 ListView 控件将称作 ListItem 对象的列表条目组织成 ...

  3. 网络软件,BA File,Disk,Photo,BackupTools等等(Mac版)

    Auto FTP Manager 6.01Crossworld CrossFTP Enterprise v1.97.7 http://www.airexplorer.net/en/index.phpC ...

  4. 【产品】张小龙《微信背后的产品观》之PPT完整文字版

    张小龙<微信背后的产品观>之PPT完整文字版 附:PPT下载地址:https://wenku.baidu.com/view/99d2910290c69ec3d5bb7573.html  微 ...

  5. Linux使用daemontools

    功能: 在使用memcached时候,怕因为一些不可预知的因素导致memcached进程死掉,而又不能及时的发现重启,可以通过daemontools来管理memcached的启动,当memcached ...

  6. 【数据结构】30、hashmap=》hash 计算方式

    前提知识 写在前面,为什么num&(length - 1) 在length是2的n次幂的时候等价于num%length n - 1意味着比n最高位小的位都为1,而高的位都为0,因此通过与可以剔 ...

  7. spark streaming 接收kafka消息之二 -- 运行在driver端的receiver

    先从源码来深入理解一下 DirectKafkaInputDStream 的将 kafka 作为输入流时,如何确保 exactly-once 语义. val stream: InputDStream[( ...

  8. webstrom sass 关于arguments 和 Output paths to refresh 设置

    第一种设置: Arguments:--no-cache --update -t expanded $FileName$:$FileNameWithoutExtension$.css Output pa ...

  9. 04-MySQL中的数据类型

    1 整体说明MYsql的数据类型#1. 数字:    整型:tinyint  int  bigint    小数:        float :在位数比较短的情况下不精准        double ...

  10. 布隆过滤器 - 如何在100个亿URL中快速判断某URL是否存在?

    题目描述 一个网站有 100 亿 url 存在一个黑名单中,每条 url 平均 64 字节.这个黑名单要怎么存?若此时随便输入一个 url,你如何快速判断该 url 是否在这个黑名单中? 题目解析 这 ...