工厂参观记:.NET Core 中 HttpClientFactory 如何解决 HttpClient 臭名昭著的问题
在 .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 臭名昭著的问题的更多相关文章
- .NET Core中NETSDK1061错误解决(转载)
NETSDK1061错误解决 在vs生成和运行都正常,发布的时候报错 .netcore控制台项目引用另一个类库 错误信息 NETSDK1061: 项目是使用 Microsoft.NETCore.App ...
- 如何在ASP.NET Core 中使用IHttpClientFactory
利用IHttpClientFactory可以无缝创建HttpClient实例,避免手动管理它们的生命周期. 当使用ASP.Net Core开发应用程序时,可能经常需要通过HttpClient调用Web ...
- 在 .NET Core 中结合 HttpClientFactory 使用 Polly(下篇)
译者:王亮作者:Polly 团队原文:http://t.cn/EhZ90oq声明:我翻译技术文章不是逐句翻译的,而是根据我自己的理解来表述的(包括标题).其中可能会去除一些不影响理解但本人实在不知道如 ...
- 在 .NET Core 中结合 HttpClientFactory 使用 Polly(中篇)
译者:王亮作者:Polly 团队原文:http://t.cn/EhZ90oq声明:我翻译技术文章不是逐句翻译的,而是根据我自己的理解来表述的(包括标题).其中可能会去除一些不影响理解但本人实在不知道如 ...
- 在 .NET Core 中结合 HttpClientFactory 使用 Polly(上篇)
译者:王亮作者:Polly 团队原文:http://t.cn/EhZ90oq 译者序一:前两天写了一篇文章 .NET Core 开源项目 Polly 介绍,在写这篇文章查看 Polly 资料时,看到了 ...
- 第十七节:.Net Core中新增HttpClientFactory的前世今生
一. 背景 1.前世 提到HttpClient,在传统的.Net版本中简直臭名昭著,因为我们安装官方用法 using(var httpClient = new HttpClient()),当然可以Di ...
- 你的眼睛背叛你的心:解决 .NET Core 中 GetHostAddressesAsync 引起的 EnyimMemcached 死锁问题
在我们将站点从 ASP.NET + Windows 迁移至 ASP.NET Core + Linux 的过程中,目前遇到的最大障碍就是 —— 没有可用的支持 .NET Core 的 memcached ...
- 解决 .net core 中 nuget 包版本冲突问题
今天在一个 asp.net core 项目中遇到了 nuget 包版本冲突的问题,错误信息如下: Version conflict detected for Microsoft.AspNet.WebA ...
- 在ASP.NET Core中用HttpClient(六)——ASP.NET Core中使用HttpClientFactory
到目前为止,我们一直直接使用HttpClient.在每个服务中,我们都创建了一个HttpClient实例和所有必需的配置.这会导致了重复代码.在这篇文章中,我们将学习如何通过使用HttpClient ...
随机推荐
- Ubuntu解决没有可安装候选软件包
解决方法:可以使用apt-cache search <package_name>寻找. 例如: E: 软件包 libqglviewer-dev 没有可安装候选 解决方法: apt-cach ...
- Element老司机开车了
orm Select选择器 坑: 1.选择器视图层一直渲染最后一个元素(value-key作为唯一标识符是关键)2.视图不更新,没有在data函数中声明变量,或者其他地方重置了已经声明过得变量 o ...
- OSI七层模型与TCP/IP五层模型
博主是搞是个FPGA的,一直没有真正的研究过以太网相关的技术,现在终于能静下心学习一下,希望自己能更深入的掌握这项最基本的通信接口技术.下面就开始搞了. 一.OSI参考模型 今天我们先 ...
- ROS-十步完成ROS-indigo安装
Ubuntu 版本是14.04.5.(这个版本的ubuntu的内核是V4.4的内核,长期维护到2019年,是14代比较稳定的,反正用这个安装就没有什么依赖的头疼的问题,安装ROS很方便,可以去清华的网 ...
- (原创)cocos lua 热更新从零开始(一)最简单demo
开发环境:WIN7 + cocos2dx 3.10 lua版本 0.学习这篇内容的基础是你要会创建并运行一个cocos lua项目 1.热更新的思想所谓的热更新,就是在线更新代码和资源.热更新的过程首 ...
- java中的BigDecimal和String的相互转换,int和String的类型转换,Integer类和String相互转换
一: /*由数字字符串构造BigDecimal的方法 *设置BigDecimal的小数位数的方法 */ 注:BigDecimal在数据库中存的是number类型. import java.math.B ...
- 如何在element-ui table 取到对应的ID的最后一个对象
来回切换3个状态值如何得到对应ID的最后一个对象 var is_push = () => { for (var i = 0; i < this.form.FaultDevices.leng ...
- centos查看系统/硬件信息及运维常用命令
[root@yan-001 ~] # uname -a # 查看内核/操作系统/CPU信息的linux系统信息命令 [root@yan-001 ~] # head -n 1 /etc/issue # ...
- [转] 微信小程序页面间通信的5种方式
微信小程序页面间通的5种方式 PageModel(页面模型)对小程序而言是很重要的一个概念,从app.json中也可以看到,小程序就是由一个个页面组成的. 如上图,这是一个常见结构的小程序:首页是一个 ...
- Conversation function
通过conversation function可以把类转成任意类型的值 #include <iostream> using namespace std; class Age { priva ...