设计错误、缺陷及文档错误等导致正确使用.NET HttpClient变得出奇地困难。所以,即使是生产环境中看似运行正常的应用程序,在负荷不满的情况下,也遭受着性能问题和运行时故障。

来自ASP.NET Monsters的Simon Timms就通过一篇题为“你正在错误地使用HttpClient,它会破坏软件的稳定性”的文章揭示了这个事实。

人们对这篇文章的反应有所不同,但大多数都显示出了失望和沮丧:

……我是唯一一个读到这种内容时会生气的人吗?我是说,如果我们发布了那样的代码,会产生什么样的后果呢?当然,我们会受到公开批评。但是,当它成为核心代码的一部分,我们只能接受它,设计变通方案,然后一次又一次地写同样的文章。

那严重破坏了最小惊讶原则。

--Voltrondemort

我想说,这表明,HttpClient要么Bug多,要么架构差。无法确定是哪一种。如果是第二种则会很有趣,就需要使用另外一种方法代替它发送Http请求。

-- Eirenarch

C#开发人员所受到的培训

为了理解我们如何陷入了这种境地,我们首先需要看下另外一个面向连接的类SqlConnection。在第一次接受如何使用IDisposable和using语句的培训时,绝大多数开发人员看到的都是类似下面这样的例子:

using (var con = new SqlConnection(connectionString)) {con.open();//这里使用连接
} //这里关闭连接

虽然针对这个示例的说明并不完善,但这个模式是正确的,而且多年来很好地服务了开发人员。然而,如果你试图将这个模式应用到另一个IDisposable类HttpClient上,则会遇到一些始料未及的问题。

具体来说,它会打开许多套接字,比你实际的需求多许多,这极大地增加了服务器的负载。而且,这些套接字实际上不会被using语句关闭。相反,它们是在应用程序停止使用它们几分钟之后才会关闭。

连接池

回到SqlConnection的例子,多数面向连接的资源都会放入连接池。当你“打开”一个数据库连接时,它首先会检查连接池中是否存在未使用的连接。如果找到了,就重用它,而不是创建一个新的连接。

同样,当你“关闭”一个SqlConnection连接时,它只是简单地将连接放回连接池。最后,一个单独的进程可以关闭长期未使用的连接,但通常来说,你可以认为它会正确地执行操作,实现性能和服务器负载的平衡。

HttpClient的工作机制并非如此。当你销毁它时,它就启动一个进程,关闭在它控制之下的套接字。也就是说,你下次请求连接时,必须重复整个连接新建过程。如果网络延迟很高,或者连接是受保护的(需要新一轮的SSL/TLS协商),就会非常痛苦。

关闭一个套接字需要花费4分钟

如上所述,关闭套接字的过程并不快。当“关闭”套接字时,你真正做的是将其状态置为TIME_WAIT。在一个预先配置好的时间窗口内,Windows将保持该套接字的状态不变,默认情况下是4分钟。这是为了防止有任何剩余的数据包仍在传输。

这大大增加了可用套接字耗尽的可能,导致运行时错误,比如“无法连接到远程服务器。System.Net.Sockets.SocketException:每个套接字地址(协议/网络地址/端口)通常只允许使用一次”。Simon Timms写到:

“通过谷歌搜索那个错误会得出一些有关缩短连接超时时间的糟糕建议。事实上,当服务器上运行的应用程序恰当地使用了HttpClient或者类似的结构,缩短超时时间会导致其他不利的结果。我们需要理解“恰当”是指什么,并修复底层的问题,而不是修改机器层的变量”。

.NET Core的性能影响

大多数仅仅使用.NET Framework完整版的开发人员不会注意到这些问题。不过,那些使用.NET Core的开发人员会有一个额外的问题,使得整个问题更加明显。

在.NET Core的RC1和RC2版本之间,引入了一个Bug,导致HttpClient.Dispose调用会产生一个介于1010毫秒和1030毫秒之间的延迟。在.NET Core 1.2之前,这个问题预计不会得到修复。

使用代理类作为解决方案

虽然HttpClient的文档没有提及,但微软模式&实践的GitHub站点介绍了一种模式。他们把HttpClient称为“代理类”,并作了如下描述:

那些代理类的创建成本很高。因此,它们应该只初始化一次,并在应用程序的整个生存期内重用。然而,这些类的使用方式经常会被误解,开发人员把它们当作资源对待,认为只能根据需要请求并快速释放[……]

Microsoft P&P建议创建一个HttpClient实例,把它存储在一个静态字段中,并在应用程序的生存期内共享该实例,而不是根据需求创建和销毁。

存在误导的文档

这将我们带回到了文档存在误导的问题。虽然是基本的样本文件,但官方文档v118(当前谷歌和必应搜索返回的结果)指出,HttpClient不支持跨线程共享。

该类型的任何公有静态(在Visual Basic中为Shared)成员都是线程安全的,而任何实例成员都不保证线程安全。

差不多就是这样。当然,如果你看一下官方文档v110,就会发现下面这段有用的描述。

HttpClient应该只初始化一次,并在应用程序的整个生存期内重用。在负载很高的情况下,为每个请求初始化一个HttpClient类会耗尽可用的套接字数量。这会导致SocketException错误。下面的例子展示了HttpClient的正确用法:

public class GoodController : ApiController
{// OKprivate static readonly HttpClient HttpClient;static GoodController(){HttpClient = new HttpClient();}
}

根据这份文档,以下方法是线程安全的。

  1. CancelPendingRequests

  2. DeleteAsync

  3. GetAsync

  4. GetByteArrayAsync

  5. GetStreamAsync

  6. GetStringAsync

  7. PostAsync

  8. PutAsync

  9. SendAsync

这似乎是MSDN文档一直存在的问题。要了解任何类的演进过程,都必须检查每个版本的文档,才能了解到新增或删除的重要段落。

DNS Bug

如果我们遵循目前为止的建议,则会出现其他的问题。Ali Kheyrollahi写道:

但事实证明,有一个更严重的问题:HttpClient不遵循DNS变化,它会(通过HttpClientHandler)独占连接,直到套接字关闭。没有时间限制!那么,DNS什么时候会发生变化呢?每次你进行蓝绿部署的时候(在Azure云服务中,当你部署到过渡槽,然后切换生产/过渡槽);每次你改变Azure流量管理器的设置;故障转移场景;许多PaaS服务的内部。

在被报道出来之前,这种情况已经存在了两年多……我在想,我们到底使用.NET构建了怎样的应用程序?

现在,如果DNS变化的原因是故障转移,则连接应该是出现了某种形式的故障,因此,这时会打开一个到新服务器的连接。但是,如果变化的原因是蓝绿部署,你切换了过渡环境和生产环境,而调用仍然会转到过渡环境——这是我们见过的一种行为,但已经通过重启从属服务器修复,我们认为这可能是Azure的一个怪象。我真是个傻瓜——它就在代码里!谁的代码?好吧,起争执了……

这个问题并不是无法修复。理论上讲,HttpClient会遵循DNS TTL(生存期)值,默认为1小时。每次过期后,HttpClient会验证该DNS记录是否仍然有效,并在必要时新建一个连接指向更新后的IP地址。

但是,由于那种情况可能不会出现,所以Kheyrollahi为我们提供了一个更简单的变通方案。借助ServicePointManager,你可以告诉HttpClient自动回收连接。

var sp = ServicePointManager.FindServicePoint(new Uri("http://foo.bar"));
sp.ConnectionLeaseTimeout = 60*1000; // 1分钟

因此,你会希望只在应用程序启动时做这件事,只做一次,并且是针对应用程序将来会访问的所有端点(如果端点是运行时确定的,就需要在发现那个端点时设置那个值)。记住,路径和查询字符串会被忽略,只有主机、端口和模式是重要的。根据场景的不同,可以将该值设为1到5分钟。

查看英文原文:Bugs and Documentation Errors in .NET's HttpClient Frustrate Developers

原文地址:http://www.infoq.com/cn/news/2016/09/HttpClient


.NET社区新闻,深度好文,微信中搜索dotNET跨平台或扫描二维码关注

.NET HttpClient的缺陷和文档错误让开发人员倍感沮丧相关推荐

  1. javascript错误_JavaScript开发人员最常犯的10个错误

    javascript错误 常见错误1:对" this'错误引用 (Common Mistake #1: Incorrect references to 'this') As JavaScri ...

  2. tomcat一段时间不操作oracle就关闭连接_操作数据库常见错误,开发人员必掌握的技能...

    人生路,靠不得任何人,只能靠自己.那些该吃的苦,一点都不能少,那是我们通向未来必须要走的路,从泥泞不堪,布满荆棘的小道上迈步,才能踏上铺满鲜花的大道. 前言 作为一个开发人员虽然有时候对于数据库这方面 ...

  3. APIJSON 自动化接口和文档的快速开发神器

    认识APIJSON APIJSON是一种JSON传输结构协议.客户端可以定义任何JSON结构去向服务端发起请求,服务端就会返回对应结构的JSON字符串,所求即所得.一次请求任意结构任意数据,方便灵活, ...

  4. angularjs常见错误_AngularJS开发人员应避免的7大错误

    angularjs常见错误 AngularJS is one of the powerful tools, used for managing the display of data. It is t ...

  5. Tencent APIJSON 零代码、全功能、强安全 ORM 库 后端接口和文档零代码,前端(客户端) 定制返回 JSON 的数据和结构

    项目介绍 零代码.全功能.强安全 ORM 库 后端接口和文档零代码,前端(客户端) 定制返回 JSON 的数据和结构. A JSON Transmission Protocol and an ORM ...

  6. 数据库约束查找的约束_数据库约束的好处,成本和文档

    数据库约束查找的约束 Constraints exist as a way to enforce or document rules within the database. How do they ...

  7. 基于ssm影视论坛系统java web jsp电影评价影评系统源码和文档

    开发环境: jdk 8 intellij idea tomcat 8.5.40 mysql 5.7 所用技术: Spring+SpringMVC+MyBatis layui jsp 演示视频: 基于s ...

  8. 《C++面向对象高效编程(第2版)》——3.11 类名、成员函数名、参数类型和文档...

    本节书摘来自异步社区出版社<C++面向对象高效编程(第2版)>一书中的第3章,第3.11节,作者: [美]Kayshav Dattatri,更多章节内容可以访问云栖社区"异步社区 ...

  9. 谈IE的浏览器模式和文档模式

    以前在 "IE8兼容视图(IE7 mode)与独立IE7的区别"一文中曾经涉及过浏览器模式和文档模式,但二者的区别却不甚了了,现在有了新的认识,再补充一下. 1.浏览器模式与文档模 ...

最新文章

  1. 库克说AI也能创造很多就业岗位,苹果加码教育布局
  2. 测试在强电磁场下基于HALL的电流传感器 ACS712-5A是否会有到影响?
  3. 腾讯地图和百度地图的PHP相互转换
  4. 2021年春季学期期末统一考试 成本管理 试题
  5. 第一次收到这么用心的感谢信
  6. 前端每日实战:34# 视频演示如何用纯 CSS 创作在文本前后穿梭的边框
  7. php 增加数组下标_PHP数组排序更改下标KEY方法
  8. virtualbox使用手记
  9. exchange 2010 部署
  10. CC2530串口通讯的实现
  11. 77GHz毫米波雷达快速chirp信号技术(二):测速原理
  12. 数据挖掘学习之路一:数据挖掘认识
  13. Linux如何验证AP6212(AP6236)的bluetooth功能
  14. catia曲面扫掠命令详解_CATIA建模教程(三)——扫掠曲面在曲面造型中的应用.pdf...
  15. python爬虫qq好友信息_qq好友空间说说爬虫
  16. 后盾网php多少钱_后盾网php视频教程:2020最热的8个后盾网免费php视频教程
  17. 5-8 哈利·波特的考试
  18. 关于socket阻塞与非阻塞情况下的recv、seng、read、write返回值问题
  19. html怎么统计总访问量,如何实现对网站页面访问量的统计?
  20. Java实现socket 客户端 长连接

热门文章

  1. C# 文件操作详解(一)---------File类
  2. 海量数据处理方法的分析
  3. 一篇文章了解Liquid模版引擎
  4. .Net下你不得不看的分表分库解决方案-多字段分片
  5. C# WPF文本框TextEdit不以科学计数法显示
  6. AspNetCoreMassTransit Courier实现分布式事务
  7. iNeuOS工业互联平台,生产过程业务联动控制
  8. ASP.NET Core Blazor WebAssembly 之 .NET JavaScript互调
  9. 分布式系统不得不说的CAP定理
  10. dotNET Core 3.X 使用 Web API