写在前面

创建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. 华为虚拟机结合VMware搭建环境测试snmp

    最近在研究zabbix监控,在实际生产环节中,我们不单单是需要对linux主机进行监控还需要对网络设备防火墙等等进行监控,那么在linux主机上我们可以安装zabbix-agernt,但是在路由器交换 ...

  2. 响应式与bootstrap

    一.单双击问题 <body> <button>按钮</button> <script src="./jQuery.js"></ ...

  3. YARN分析系列之一 -- 总览YARN组件

    下图简单明了的描述了hadoop yarn 的功能是如何从 hadoop 中细化出来的. 注:图片来自 https://apprize.info/php/hadoop/9.html Hadoop 从 ...

  4. 附005.Kubernetes身份认证

    一 Kubernetes访问 1.1 Kubernetes交互 与Kubernetes交互通常有kubectl.客户端(Dashboard).REST API请求. 1.2 API访问流程 用户使用k ...

  5. Unity AssetBundle,Asset,GameObject之间的联系

    一.问题 首先,这里说明一下,我这边的GameObject有点笼统,就是表达的是游戏中的具体实例. 二.概念 1)Asset是什么? 游戏中具体的资源,像texture,mesh,material,s ...

  6. HDFS Java API 的基本使用

    一. 简介 二.API的使用         2.1 FileSystem         2.2 创建目录         2.3 创建指定权限的目录         2.4 创建文件,并写入内容 ...

  7. 【win10家庭版】删除文件提示没有权限最简单的方式(已验证)

    趁着618入了新本本,预装了家庭普通版Win10,但是实际使用中遇到了一些问题.问题不大,但是着实反人类,在此吐槽! 问题: 首先,进入系统会提示你建一个账号,建立完成登录系统.本账户拥有Admini ...

  8. 统一资源定位符URL

    Uniform Resource Locate--URL 用途:通过URL访问web网页:通过URL使用其它的Internet应用程序,例如FTP,Telnet(远程登录服务):对应IE浏览器窗口中的 ...

  9. node.js的异步I/O、事件驱动、单线程

    nodejs的特点总共有以下几点 异步I/O(非阻塞I/O) 事件驱动 单线程 擅长I/O密集型,不擅长CPU密集型 高并发 下面是一道很经典的面试题,描述了node的整体运行机制,相信很多人都碰到了 ...

  10. 使用 cxf的程序 在win10 测试部署时报空指针异常

    2018-11-08 15:50:55.072 DEBUG 21524 --- [nio-8080-exec-1] o.s.b.w.s.f.OrderedRequestContextFilter  : ...