在 .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. PL/SQL连接远程oracle数据库配置

    本人电脑:win10  64位, plsql:支持32位 一.安装好PL/SQL客户端 二.下载解压oracle客户端 1.instantclient 我下载的是oracle11g 32位的insta ...

  2. 3.让linux 增加 wget 命令

    Wget主要用于下载文件,在安装软件时会经常用到   直接执行命令 : sudo yum -y install wget   就可以使用wget了

  3. ado.net 使用:ExecuteReader 无法获取输出参数

    解决方法: 要获取到输出参数.需要连接关闭之后才行. 一般都是用using把打开数据库连接的reader包起来

  4. Centos7 设置vim 显示文本不同颜色

    Centos7 设置vim 显示文本不同颜色 本人在查找设置 centos7 vim 文本显示颜色时, 学习了作者: luffy5459 (博客连接:https://blog.csdn.net/fei ...

  5. idea一款颜值很高的theme

    在idea的plugins搜索theme,能看到一款人气值超高的插件,下载使用了确实很漂亮!

  6. 51nod 1423 最大二“货” 单调栈

    利用单调栈,高效求出每个区间内的最大值和次大值的亦或值. 先正向扫描,利用单调递减栈,若当前栈为空栈,则直接压入栈中,若为非空栈,弹出栈顶元素,每弹出一个元素,则求一次亦或值,保留最大值 接着进行反向 ...

  7. C#学习笔记-域用户认证(一)

    public Boolean ValidateDomainUser(string Domain, string UserName, string Password) { DirectoryEntry ...

  8. python 打印Linux中文编码字符

    2018-10-12 12:02:15 星期五 python -c "print '\346\234\215\345\212\241\345\231\250\346\217\220\344\ ...

  9. python学习第23天

    isinstance和issubclass 反射: 反射对象中的内容 反射类中的内容 反射本文件中的内容 反射模块中的内容

  10. 【原创】Linux基础之sudo

    sudo允许用户以其他用户的身份(比如root)执行命令,比如切换用户.执行命令.读写文件等: 配置 sudo配置在:/etc/sudoers ## Sudoers allows particular ...