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

设计错误、缺陷及文档错误等导致正确使用.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跨平台或扫描二维码关注

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/328523.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python监听剪贴板_Python监听剪切板实现方法代码实例

第一种import win32clipboardimport time#速度快 容易出错class niubi():def lihai(self):while True:#jianting().main()t jianting().main()print(t)class jianting():def clipboard_get(self):"""获取剪贴板数据"""win32clipboard.OpenClipb…

新闻发布项目——业务逻辑层(UserService)

package bdqn.newsManageServlet.Service;import bdqn.newsManageServlet.entity.User;/*** 用户信息* author Administrator**/ public interface UserService {public User getLoginUser(String uName,String pwd, String msg);}

二叉树总结挺好的很好记忆

https://blog.csdn.net/fightforyourdream/article/details/16843303 面试大总结之二:Java搞定面试中的二叉树题目 2013年11月20日 14:04:27 chiiis 阅读数:25438更多 个人分类: AlgorithmInterview 这是本系列的第二篇,与前一…

免费开源分布式系统日志收集框架 Exceptionless

前言 从去年就答应过Eric(Exceptionless的作者之一),在中国会帮助给 Exceptionless 做推广,但是由于各种原因一直没有做这件事情,在此对Eric表示歉意。:) Exceptionless 简介 Exceptionless 是一个开源的实时的日志收集…

如何安装mysql5.5.6_centos6安装mysql5.5.53

MysqL官网下载MysqL的red hat linux安装包下载地址为:http://dev.MysqL.com/downloads/MysqL/5.5.html#downloads下载后的文件为:MysqL-5.5.53-1.el7.x86_64.rpm-bundle.tar解压 tar 包 :tar -xvf MysqL-5.5.53-1.el7.x86_64.rpm-bundle.tar移…

java职业规划

百度搜索java职业规划好多啊啊啊

避免同步死锁

翻译自 避免同步死锁在我之前的文章“ Double-Checked Locking:Clever,but Broken ”(JavaWorld,2001年2月),我描述了几种常用的避免同步的技术实际上是不安全的,并建议了一个“如有疑问&#…

程序员小测试:保守派 vs 自由派

最近,我在阅读 Steve Yegg 的文集《程序员的呐喊》。 这是一本非常有趣的书,里面甚至包含了一个小测试(原文),区分一个程序员到底是保守派还是自由派。 下面一共有十个问题,每个问题都有 A 和 B 两个选项&a…

新闻发布项目——业务逻辑层(newsTbService)

package bdqn.newsManageServlet.Service;import java.util.List;import bdqn.newsManageServlet.entity.newsTb;/*** 新闻业务逻辑层的接口* author Administrator**/ public interface newsTbService {//分页查询public List<newsTb>getPagingNews(int pagesize,int pa…

django mysql 创建表_关于 django ORM 中,数据库建表方式的问题

本人以前是做客户端的&#xff0c;做后端没多久。一直有一个问题困扰我&#xff0c;正好现在手上有一个系统在做。系统逻辑是写一个爬虫在网上爬取一个网站的信息&#xff0c;经过加工处理后&#xff0c;使用 django 来展示和交互。表结构简化为三张表A:爬虫爬取得到的信息B:数…

遍历多叉树

https://www.jianshu.com/p/dee8284b2dc4 beg4 关注 2018.03.22 15:14* 字数 334 阅读 172评论 0喜欢 1 随便画一个树,写代码遍历它 OK,树的结构这么描述 public class TreeNode {private String name;private TreeNode parent;private List<TreeNode> children new …

tomcat与apache的面试题

转载自 tomcat与apache的面试题tomcat与apache有哪几种连接方式&#xff1f; Tomcat 与 Apache 有三种连接方式&#xff0c; 1、JK方式 这是最常见的方式。JK 是通过 AJP 协议与 Tomcat 服务器进行通讯的&#xff0c;Tomcat 默认的 AJP Connector 的端口是 8009。JK 本身提供…

使用 Exceptionless 作为 Log Server 搭配 NLog 记录系统日志

昨天的文章<免费开源分布式系统日志收集框架 Exceptionless>反响很大,今天推的一篇是续集,文章来自于宝岛台湾的MVP, 让 Exceptionless 建置 Log Server,性能不用担心,用的是大名点点的ELK组合. 前言 痾...久违的新文章&#xff0c;让我的眼角流下了蛋蛋忧伤的泪珠.... 今…

elementui下拉框选择图片_element ui下拉框如何实现默认选择?

为什么我这样写没反应呢运营商级别 <el-optionv-for"item in options1":label"item.label":value"item.value"></el-option>export default{data(){return{selected:,options1:[{value: 选项1,label: 省级运营商}, {value: 选项2,la…

程序员成长之路 java面试指导(作者说的极好要看) 静下心看

https://blog.csdn.net/weixin_41780944/article/details/79429769 从面试官的角度分享一些后端校招经验&#xff08;作者说的极好要看&#xff09; https://blog.csdn.net/qq_34337272/article/details/80875016 除了手写代码和计算机基础&#xff0c;剩下的方面都是可选项…

[MySQL] 二进制的应用场景

前言 MySQL 是一种常用的关系型数据库管理系统&#xff0c;广泛应用于各种软件和网站开发中。在 MySQL 中&#xff0c;数据以二进制文件的形式存储在硬盘上。这些二进制文件不仅可以提供数据的持久化存储&#xff0c;还可以在不同的应用场景中发挥重要作用。本文将介绍 MySQL …

各种面试题(二)

转载自 各种面试题(二)1、面向对象的特征有哪些方面? 封装&#xff1a;通常认为封装是把数据和操作数据的方法绑定起来&#xff0c;对数据的访问只能通过已定义的接口。 多态性&#xff1a;多态性是指允许不同子类型的对象对同一消息作出不同的响应。简单的说就是用同样的对象…

亚马逊如何变成 SOA(面向服务的架构)?

上一篇文章&#xff0c;我摘录了《程序员的呐喊》。这本书有趣的内容太多&#xff0c;今天再摘录一段。 1、 亚马逊公司不仅是世界最大的网络书店&#xff0c;还是世界最大的云服务商。它是怎么实现从电商到云商的转变呢&#xff1f; 一切都是CEO杰夫贝索斯促成的&#xff0c;他…

新闻发布项目——业务逻辑层(commentService)

package bdqn.newsManageServlet.Service;import java.util.List;import bdqn.newsManageServlet.Dao.commentDao; import bdqn.newsManageServlet.Dao.Impl.commentDaoImpl; import bdqn.newsManageServlet.entity.comment;/*** 新闻评论的业务逻辑层* author Administrator**…

linux 安装mysql 8.0_Linux安装mysql 8.0的详细方法介绍(代码示例)

本篇文章给大家带来的内容是关于Linux安装mysql 8.0的详细方法介绍(代码示例)&#xff0c;有一定的参考价值&#xff0c;有需要的朋友可以参考一下&#xff0c;希望对你有所帮助。经过一番努力下载mysql文件&#xff0c;我们可以开始Mysql8.0的安装了。解压文件// 解压文件生成…