在 .NET Framework 与 .NET Core 中 HttpClient 有个臭名昭著的问题,HttpClient 实现了 IDispose 接口,但当你 Dispose 它时,它不会立即关闭所使用的 tcp 连接,而是将 tcp 连接置为 TIME_WAIT 状态,240秒(4分钟)后才真正关闭连接。对于高并发的场景,比如每秒 1000 个请求,每个请求都用到 HttpClient ,4分钟内会堆积24万个 tcp 连接,这样的连接爆棚会拖垮服务器。为了避开这个坑,通常采用的变通方法是使用静态的 HttpClient ,但会带来另外一个臭名还没昭著的问题,当 HttpClient 请求的主机名对应的 IP 地址变更时,HttpClient 会蒙在鼓里毫不知情,除非重启应用程序。

为了彻底解决这两个问题,解救广大 .NET 开发人员,HttpClientFactory 在 .NET Core 2.1 中闪亮登场。那 HttpClientFactory 是如何解决问题的呢?让我们一起来参观一下这个有点特别的工厂。

工厂地址在微软市 github 区 aspnet 街 105584022 号 ,https://github.com/aspnet/HttpClientFactory

参观 HttpClientFactory 之前先更多了解一下 HttpClient 的 Dispose 问题。

HttpClient 被 Dispose 时产生 TIME_WAIT 状态的 tcp 连接的本质是在 HttpClient 被 Dispose 时,它所依赖的 HttpMessageHandler 也被 Dispose 了,管理 tcp 连接的正是 HttpMessageHandler ,HttpMessageHandler 是抽象类,落实到实际应用场景通常是 SocketsHttpHandler ,SocketsHttpHandler 通过 HttpConnectionPoolManager 管理着 HttpConnectionPool ,池中养着一堆 HttpConnection 对应的 tcp 连接,Dispose SocketsHttpHandler 影响的通常不是一个 tcp 连接,而是一池 tcp 连接,也就是会将整个池中的所有 tcp 连接都置于 TIME_WAIT 状态,并发量越大,池中的连接越多,Dispose 的杀伤力越大,大到可以会引发 socket exhaustion 。所以,要想解决这个问题就要减少 Dispose 操作,最极端的情况就是使用静态的 HttpClient ,永不 Dispose ,但如前所述这样做的副作用很大。

既要 Dispose HttpClient,又要控制好火候,这是解决这个棘手问题的关键,而 HttpClientFactory 也正是从这个角度出发打造出了一个可定时 Dispose 的工厂。

HttpClientFactory 创建 HttpClient 实例的主要代码如下:

public HttpClient CreateClient(string name)
{
//...
var entry = _activeHandlers.GetOrAdd(name, _entryFactory).Value;
var client = new HttpClient(entry.Handler, disposeHandler: false);
StartHandlerEntryTimer(entry);
//..
return client;
}

为了解决 HttpMessageHandler 的 Dispose 问题,HttpClientFactory 工厂设计制造出了一款新型 HttpMessageHandler —— LifetimeTrackingHttpMessageHandler ,一个有保质期的 HttpMessageHandler (默认是 2 分钟),新生产的 LifetimeTrackingHttpMessageHandler (之后简称 handler)会被放入 _activeHandlers ,过了保质期的 handler 会被放入 _expiredHandlers (有个 Timer 专门在 ExpiryTimer_Tick 回调方法中负责检查保质期), 而在 _expiredHandlers 中的 handler 们会被进一步检查,有个 CleanupTimer 专门在 CleanupTimer_Tick 回调方法中每隔10秒负责检查,进一步检查什么呢?检查这些过期产品(handler)是否可以作废(Dispose),怎么检查的?通过 WeakReference ,代码如下:

internal class ExpiredHandlerTrackingEntry
{
private readonly WeakReference _livenessTracker; public ExpiredHandlerTrackingEntry(ActiveHandlerTrackingEntry other)
{
Name = other.Name; _livenessTracker = new WeakReference(other.Handler);
InnerHandler = other.Handler.InnerHandler;
} public bool CanDispose => !_livenessTracker.IsAlive;
public HttpMessageHandler InnerHandler { get; }
public string Name { get; }
}

如果 _expiredHandlers 中的 handler 已经被 GC 回收(同时也说明对应的 HttpClient 也被 GC 回收),那就 Dispose 掉它。

HttpClientFactory 就是这样通过 2 个定时器有条不紊地控制着 Dispose HttpMessageHandler 释放 TCP 连接的火候,避免在同一时间 Dispose 太多 HttpMessageHandler 引发的 socket exhaustion 解决了 HttpClient 臭名昭著的问题。

工厂参观记:.NET Core 中 HttpClientFactory 如何解决 HttpClient 臭名昭著的问题的更多相关文章

  1. .NET Core中NETSDK1061错误解决(转载)

    NETSDK1061错误解决 在vs生成和运行都正常,发布的时候报错 .netcore控制台项目引用另一个类库 错误信息 NETSDK1061: 项目是使用 Microsoft.NETCore.App ...

  2. 如何在ASP.NET Core 中使用IHttpClientFactory

    利用IHttpClientFactory可以无缝创建HttpClient实例,避免手动管理它们的生命周期. 当使用ASP.Net Core开发应用程序时,可能经常需要通过HttpClient调用Web ...

  3. 在 .NET Core 中结合 HttpClientFactory 使用 Polly(下篇)

    译者:王亮作者:Polly 团队原文:http://t.cn/EhZ90oq声明:我翻译技术文章不是逐句翻译的,而是根据我自己的理解来表述的(包括标题).其中可能会去除一些不影响理解但本人实在不知道如 ...

  4. 在 .NET Core 中结合 HttpClientFactory 使用 Polly(中篇)

    译者:王亮作者:Polly 团队原文:http://t.cn/EhZ90oq声明:我翻译技术文章不是逐句翻译的,而是根据我自己的理解来表述的(包括标题).其中可能会去除一些不影响理解但本人实在不知道如 ...

  5. 在 .NET Core 中结合 HttpClientFactory 使用 Polly(上篇)

    译者:王亮作者:Polly 团队原文:http://t.cn/EhZ90oq 译者序一:前两天写了一篇文章 .NET Core 开源项目 Polly 介绍,在写这篇文章查看 Polly 资料时,看到了 ...

  6. 第十七节:.Net Core中新增HttpClientFactory的前世今生

    一. 背景 1.前世 提到HttpClient,在传统的.Net版本中简直臭名昭著,因为我们安装官方用法 using(var httpClient = new HttpClient()),当然可以Di ...

  7. 你的眼睛背叛你的心:解决 .NET Core 中 GetHostAddressesAsync 引起的 EnyimMemcached 死锁问题

    在我们将站点从 ASP.NET + Windows 迁移至 ASP.NET Core + Linux 的过程中,目前遇到的最大障碍就是 —— 没有可用的支持 .NET Core 的 memcached ...

  8. 解决 .net core 中 nuget 包版本冲突问题

    今天在一个 asp.net core 项目中遇到了 nuget 包版本冲突的问题,错误信息如下: Version conflict detected for Microsoft.AspNet.WebA ...

  9. 在ASP.NET Core中用HttpClient(六)——ASP.NET Core中使用HttpClientFactory

    ​到目前为止,我们一直直接使用HttpClient.在每个服务中,我们都创建了一个HttpClient实例和所有必需的配置.这会导致了重复代码.在这篇文章中,我们将学习如何通过使用HttpClient ...

随机推荐

  1. PHP微信公众号JSAPI网页支付(上)

    一.使用场景以及说明 使用场景:商户已有H5商城网站,用户通过消息或扫描二维码在微信内打开网页时,可以调用微信支付完成下单购买的流程. 说明:1.用户打开图文消息或者扫描二维码,在微信内置浏览器打开网 ...

  2. Selective Search for Object Recognition(理解)

    0 - 背景 在目标检测任务中,我们希望输入一副图像,输出目标所在的位置以及目标的类别.最常用的算法是滑动窗口方法,但滑动窗口其实相当于穷举图像中的所有子图像,其效率低且精度也受限.该论文提出一种新的 ...

  3. library 显示所有的数据

    <?php  $conn = @mysql_connect('localhost', 'root', ''); if($conn) {  echo "连接成功"; }else ...

  4. C#基础零碎知识点摘录

    1.类分为静态类个非静态类(实例类) 静态类不能创建对象,使用方法时,直接类名.方法名(),常用的静态类有Console类 实例类:创建对象时通过对象调用类的方法 2.当我们声明一个类成员为静态时,意 ...

  5. thinkpad 睡眠唤醒后热键功能正常,但屏幕无法显示状态/进度条/图标

    由于博主比较习惯笔记本开盖即用,合盖即走,不大习惯开机关机(毕竟SSD速度杠杠滴^_^).可是发现笔记本长时间睡眠乃至休眠唤醒后,使用thinkpad热键,虽然可以调节,但屏幕不显示调节状态了.解决步 ...

  6. Java的家庭记账本程序(K)

    日期:2019.3.10 博客期:043 星期日 呕吼~这里是编程菜鸟小Master,今天加油的把第二个模板套用了,更改了许多的设定,我想这一个程序的网页版也就到这里结束了,下面是一部分的展示图,想要 ...

  7. 如何给PDF设置全屏动画

    PPT文件可以播放全屏,并且可以实现飞入.分割.闪烁等动画模式播放.那么PDF文件可以吗?我们想要给PDF文件加入动画效果应该怎么做呢,也有很多的小伙伴不知道该怎么把PDF文件切换为全屏动画模式想要知 ...

  8. element-ui修改全局样式且只作用于当前页面

    1)修改组件的样式,但是只作用于当前页面,其他页面不受影响,做法有两种: 法一:使用关键字“/deep/” 1)在当前页面添加样式: <style lang="scss" s ...

  9. 倒影问题(reflect:below)

    这个例子灵感来源于实现一个登录框下方的倒影: .box { width: 300px; height: 200px; border: 1px solid #1f637b; -webkit-box-re ...

  10. Coverity代码扫描工具

    1.说明:Coverity代码扫描工具可以扫描java,C/C++等语言,可以和jenkins联动,不过就是要收钱,jenkins上的插件可以用,免费的,适用于小的java项目 2.这是Coverit ...