代码改变世界

【经验总结】.Net的并发连接数限制问题

2009-12-18 22:03  GUO Xingwang  阅读(6863)  评论(15编辑  收藏  举报

  最近在项目中遇到了很棘手的问题,查到最后发现是由于HttpWebRequest对于同一个domain的请求建立的连接数同时不能超过两个的限制造成的。我用这篇短文把对这个问题的处理记录下来,同时也做为个人的经验积累吧,希望对大家有所帮助。

  这里所说的连接并发数限制是指对于同一个domain发起的最大连接数。其实在大多数微软的产品或组件中都存在这个限制,一般这个数值是2/4个,也就是说在默认情况下,对于同一个domain同时最多有两个连接处于建立状态。例如:熟悉javascript编程的人可能很清楚这个限制,在使用ajax时创建XmlHttpRequest对于同一个domain就有这个限制,实际上这个限制是继承与微软的IE浏览器的默认限制,也就是说IE浏览器本身对于同一个domain发起的连接数同时最多存在两条链路,由于Http协议是一个无状态的协议,连接也在Response返回来以后断掉(除非指定了keep-alive可以保持连接,这样可以保持一段时间),当然对于像IE这种多模式的客户端来说,这两条连接同时建立的时候几乎很少,甚至不可能,当然在comet技术应用时是很模拟。所以对于这种模式下的客户端来说这个连接限制没有那么明显,甚至很多人都不知道这个限制的存在。当然微软做这个限制是道理的,我想最大的好处可能就是防止一个客户端发起太多的并行连接来攻击服务器了(我的拆测)。但是这个连接限制在同时也阻碍了单模式下的服务器编程,当然我们可以修改这个默认值,但是毕竟还是比较麻烦。而且这个修改对于一个应用程序来说来说是全局的(我采取的方式,因为对HttpWebRequest对象的修改并不适合我的项目),当然这个修改也可以针对具体的HttpWebRequest对象进行设置。具体如何修改可以参照一下这篇文章http://www.qqdao.org/Lists/Posts/Post.aspx?List=751e96b2%2D405a%2D41cf%2Dadce%2Dc6a5b9a8c157&ID=22&RootFolder=%2A

  我的问题是出现在刚刚上线的项目中,我的这个项目是一个服务器端程序,内部使用了HttpWebRequest去请求一个网站的url。在这个项目中请求的次数非常频繁。

  问题表现:刚上线的服务就要承担很大的压力,据统计大概每秒钟有至少2000多个请求需要处理,有四台服务器做负载实现。但是发现在功能环境下运行很好的程序到了生产环境很多调用都失败了,成功的占了比例不大。失败的请求大都报如下错误“System.Net.WebException: The request was aborted: The request was canceled.”。但是查看网站的压力并不大,可以说是很轻,在用netstat -abn | find "xxxx"命令查看到网站的连接,发现只有两个已经建立的连接,当然这两个连接是并行的,并且是keep-alive的连接。从问题可以看出,大多数请求实际上是发送失败了,之后被取消了。但是这个发送失败并不是网站一侧没有资源来应答,而是请求在客户端就没有发出去数据包,可以说请求本身就没有发送出去。这个问题导致网站对于请求的吞吐能力再强也没有用,客户端请求发送失败。给人的感觉就像请求积压很厉害并且最后放弃了。

  问题排查:这个问题对于没有经验的人来说却实无从下手,方法就是尽量排除一些可能的原因,剩下最后的几个原因时进行验证。首先我猜测可能是线程池中的线程用尽了,但是后来抓了dump进行分析发现并不是这个原因。在经过一个晚上沉重的思考以后,第二天早上似乎有了一点灵感,已经很确定是由于连接数的限制了。我这样说是因为我考虑到网站端如果长时间没有返回,那么这个连接就一直在占用,当两个连接都在被占用时,再发送请求是就会由于没有及时得到可用的连接而失败(虽然实际情况可能比这要复杂的多,例如缓存请求,进行重试等,但是请求很频繁是,发送的能力是有限的,总会有一些请求没有及时发送出去而抛出异常,这个问题就好比通过一个漏斗向一个容器中注入水一样,如果流入的速度大于流出的速度会使漏斗慢慢达到盛满水,这样再继续时肯定就会流到外面了,也就是这里出现了异常,也就是多出来的会放舍弃掉。

  问题解决:既然知道了原因,那么解决起来自然也就比较容易了,修改.Net的这个默认并发连接数可以通过很多种方式,可以通过在程序初始化时执行以下代码完成ServicePointManager.DefaultConnectionLimit = 500;,也可以通过修改配置文件来达到同样的效果,由于程序已经上线,所以我是通过修改配置完成的。这个东西自然在线下也做了很多测试,发现确实是这里的问题,于是晚上做了一个更新,第二天早上发现基本所有调用都成功了,不成功的也是网站没有返回结果超时导致的,真是庆幸。同时发现以前经常报的错也不见了,正如我所料。当然我这限制设置为500而没有设置到1000主要是我想客户端的端口开的不会太多。

  总结:这个配置我觉得是一个限制,不能设置的太大也不能太小。其实搞计算机的需要很多经验的同时,灵感也很重要,这个问题就是我在当天晚上没有办法的情况下入睡以后,第二天醒来以后突然觉察到的,之后到了公司就开始测试和验证,没想到还真解决了问题,但是有一点还是特别提醒,无论是用什么协议栈发送调用或消息,最好都不要使用for语句压消息,这会太调用量大时会出现问题。