Hello World
Spiga

关于Windows频繁打开关闭端口时出现的问题

2010-08-09 18:03 by 老赵, 16847 visits

最近事情很多,人也懒,东西看了不少,也想到过一些东西,但就是懒得写。现在记录一下前两个星期做一个压力测试时出现的现象,希望重开一个好头。简单地说,这是个从Windows Server连接Linux下的MongoDB服务时出现的问题。MongoDB使用的是自定义的二进制协议,客户端使用普通的TCP连接进行连接后再读写数据。在以前的测试中,我使用的都是建立少量连接,每个连接进行多次操作,而这次则是对“应用程序”进行压力测试,因此需要不断地开启及关闭连接——频率大约是每秒4、500次吧。

我使用的环境是Windows Web Server 2008 R2,MongoDB部署在Cent OS上,双方都是64位操作系统。压力测试刚开启时一切顺利,性能也比较令人满意,但是不久后便会抛出这样的异常:

由于系统缓冲区空间不足或队列已满,不能执行套接字上的操作。

一开始我以为是程序里有哪个地方没有释放连接,于是检查了程序代码,觉得没有问题;后来又直接使用mongodb-csharp进行频繁连接关闭,结果还是出现了同样的错误,于是我又怀疑是驱动本身的问题,但是看了看讨论组中似乎又没有人汇报过这个问题;于是我又换了个思路,使用了Java平台上的驱动写了个简单的测试程序,居然还是得到了这个错误。由此我确定了两点:

  • 这很可能不是mongodb-csharp这个驱动程序的问题。当然,要确定这一点还需要更多测试,例如在mono上使用这个驱动。
  • 这是操作系统方面的问题,因为.NET和Java都给出了同样的错误信息,甚至和当前程序的语言文化设置无关。

还有一个细节:在直接使用驱动进行插入操作的时候,发现无论使用多少线程同时进行,最终永远是在插入了16370-16380条记录之后停止,这意味着每次都是打开关闭了确定次数之后出现的错误,这很有可能是一个操作系统限制所致的结果。因此,我使用这段错误信息在网上寻找解决方案,原因有很多,大都不是我需要的。顺便一提,只有中文的错误信息真是很难找到合适的结果,因此我不得不通过几个关键字,再连蒙带猜地得到了错误的标准英文翻译:

An operation on a socket could not be performed because the system lacked sufficient buffer space or because a queue was full.

有了这段信息,找起答案来就简单多了,例如KB上找到了这样一条记录,说是在Windows Vista及2008中,Tcp/IP动态端口的范围调整到49152至65535,做一个简单的减法可以发现我们可以使用16384个接口,和我们之前看到的记录数量大致相同,基本可以确定是频繁地打开关闭操作造成客户端的动态端口用尽的问题。KB上也给出了解决方法,只要使用netsh命令便可以进行设置。

不过有意思的是,我在此之前还找到了另一条记录,说是在HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters下面可以增加一个MaxUserPort参数,指定程序可以使用的端口范围,它的默认值是5000,也就是端口范围是1025至5000,这也是上一条记录中说Windows之前的端口策略,它的“适用范围”已经不包括2008系统,但我鬼使神差地将MaxUserPort设置为65534(十进制)之后,原本只能插入16000多条记录的程序已经能够插入数万条,这意味着修改的确生效了。

当然,这么做还是没有解决问题,总不见得插入这么多条记录之后还是失败吧。其实第二条记录里还写到,有一个TcpTimedWaitDelay参数,表示一个关闭后的端口等待多久之后可以重新使用,顺着这个信息我找到了《TCP/IP Registry Values for Windows Server 2008》这样一篇文章,描述Vista与2008系统中各种TCP/IP相关的参数,其中自然包括了TcpTimedWaitDelay,它的默认值为120,表示端口关闭后120秒才能重新使用。

于是我们来算一下,假设有60000个端口可用,如果在120秒内消耗完毕,则每秒最多使用500个端口,这远远低于MongoDB的性能瓶颈,甚至接近了一个Web应用程序的需求——根据压力测试,我们单台Web服务器每秒可以处理接近200个动态请求,这意味着平均每个请求只能使用2.5个连接。根据文档,我将TcpTimedWaitDelay设成最短的30秒,这意味着我们可以每秒开启关闭2000个端口,平均每个请求使用10个连接。够了。

这个问题就这样解决了,说实话很简单,也就是个“知道就能解决”的配置问题。当然现在这个方式并不算太理想,更好的方式应该是利用连接池,这样便不会开启/关闭大量的TCP/IP连接,默认的端口数量也已经足够了,更重要的是这也可以省下很大的开销——因为经过测试,即使是最复杂的ASP.NET页面,只要不涉及MongoDB,每秒也能处理6500多个请求,而目前每秒200个动态请求,从数字上看也远低于MongoDB的能力,我们有理由相信目前的性能似乎是卡在连接的打开/关闭上了。

只可惜目前mongodb-csharp的连接池实现有bug,用于清理连接的维护进程居然会让造成明显的中断,甚至在频繁使用十几分钟后还抛出了异常。有机会的话我再看看吧,但我总觉得它目前的实现过于复杂了,我估计都可以说是面向对象的“经典”使用案例了。

最后再来一提,话说我目前使用的是64位的Windows Web Server 2008 R2系统,功能强大,价格便宜授权宽松,最多允许使用到32GB内存,作为Web服务器我很满意。

相关阅读

Creative Commons License

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言

Add your comment

24 条回复

  1. codingguy
    58.58.38.*
    链接

    codingguy 2010-08-09 18:33:41

    沙发!这个就是沙发!

  2. fjzhou
    58.41.16.*
    链接

    fjzhou 2010-08-09 19:18:07

    TCP连接处于TIME_WAIT的时候用SO_REUSEADDR可能会好点。不过这个状态无法避免,没辙。

  3. fjzhou
    58.41.16.*
    链接

    fjzhou 2010-08-09 19:18:43

    发现两个下划线之间的字变成斜体了。。。。。老赵的设置么?

  4. 老赵
    admin
    链接

    老赵 2010-08-09 19:42:49

    @fjzhou

    这个Markdown标记,可以看一下帮助,呵呵。

  5. stainboy
    116.228.152.*
    链接

    stainboy 2010-08-10 08:48:59

    老赵写文章越来越文学化了,条理清晰,耐人寻味,像推理小说,很不错:)

    不过最后的小广告。。。呵呵,其实我也用2008R2,不过是软激活的,公司丫的没钱买正版。。。

  6. 不及格的程序员-八神
    119.118.215.*
    链接

    不及格的程序员-八神 2010-08-10 09:10:10

    这个问题,如果是了解windows的人遇到,就不是问题了.

    老赵,盛大出品的读书器有你买的那个好没?

  7. 老赵
    admin
    链接

    老赵 2010-08-10 10:04:22

    @不及格的程序员-八神: 老赵,盛大出品的读书器有你买的那个好没?

    我也搞不到内测号呢……

  8. 老赵
    admin
    链接

    老赵 2010-08-10 10:06:02

    @stainboy: 其实我也用2008R2,不过是软激活的,公司丫的没钱买正版。。。

    我不用Enterprise,Standard之类的,就用Web Server,很便宜,如果是这个我不信公司会没钱,嘿嘿。

  9. FMax
    59.37.15.*
    链接

    FMax 2010-08-10 11:51:19

    频繁使用十几分钟后还抛出了异常

    这个问题遇到过,隔段时间就出来次,原来以为是网络出了问题,因为等个几秒钟后又恢复了。你估计是cleanup里什么操作造成的呢?

  10. fjzhou
    61.172.241.*
    链接

    fjzhou 2010-08-10 13:28:03

    Bambook尺寸太小,才6寸,只能看纯文本的小说了。不过价格和太阳能充电相当吸引人…… 盛大内部也要自己申请的,不能占用内侧名额…… from SDO

  11. 老赵
    admin
    链接

    老赵 2010-08-10 13:52:55

    @FMax

    这是现象,原因是端口用完了,需要等一段时间才能复用。

  12. shanyou
    183.17.150.*
    链接

    shanyou 2010-08-11 08:07:20

    这在 Windows Server 下很常见,是tcp的time_wait状态的问题

    http://www.cnblogs.com/shanyou/archive/2010/07/09/1774075.html

  13. 老赵
    admin
    链接

    老赵 2010-08-11 09:06:58

    @shanyou

    多谢补充,我在你那儿又看到郑昀的文章,一并引来了,呵呵。

  14. Rain Shan
    222.92.145.*
    链接

    Rain Shan 2010-08-13 10:20:14

    希望老赵能多写一些关于F#方面的资料。 中文资料几乎没有,E文资料看着头疼,我承认我也很懒。。。。

    最近在看老赵的Linq to sql 方面的资料,虽然是2年前写的,但是很实用。 公司不让用存储过程,而且只允许用 .net frarmwork 2.0 非常的郁闷,很多东西都必须自己重新来设计。

    我想利用 linq 的特性,自己封装一套ORM出来,不知道老赵有什么好的建议啊。 还有,我一提linq to sql,公司的“老大们”就说执行效率是如何如何的慢,我做测试给他们看,他们居然说“不让你用,你就不要用。” 唉,有苦难言啊,来老赵这里诉诉苦。。。

  15. 阿斯顿发放
    112.64.163.*
    链接

    阿斯顿发放 2010-08-13 12:26:14

    楼上的用nhirbenate吧

  16. Rain Shan
    222.92.145.*
    链接

    Rain Shan 2010-08-13 12:44:20

    提过,同样不让用,说很慢。。。

  17. 你二大爷
    220.248.94.*
    链接

    你二大爷 2010-08-13 15:19:55

    博主精神有问题,需要电疗。。。。。。。

  18. 柳晛
    221.216.55.*
    链接

    柳晛 2010-08-13 17:51:07

    web版2008公司还真买不起,超过50元就买不起。 我们是创业中的公司,成立一年亏损一年,能交得起电费就不错

  19. 老赵
    admin
    链接

    老赵 2010-08-14 22:32:28

    @柳晛

    哈哈,我不信,难道一点钱都不赚?难道连工资都不发?

  20. Yong
    211.144.200.*
    链接

    Yong 2010-08-15 13:35:18

    我对于MongoDB不熟悉,但是对于大量并发的TCP连接,毫无疑问需要采用连接池的方法, 这样连节约连接创建所带来的开销。

  21. zffl
    61.172.247.*
    链接

    zffl 2010-11-05 10:31:13

    翻出来老文章,刚刚在测试tornado也遇到这个问题了。

  22. junxy
    118.249.111.*
    链接

    junxy 2011-08-05 11:57:56

    其中看到一段觉得很经典很有体会,就是一些比较“罕见”的问题打中文是搜不到什么只好到google.com里“瞎蒙”,baidu更是忽略,hehe

  23. vincent
    59.46.173.*
    链接

    vincent 2020-09-14 19:44:53

    UDP发消息也遇到过,需要对UDP多级转发器做压力测试,在转发到后一级的时候,端口用尽,想问一下这个在windows上的问题,linux上有吗?压力测试频繁创建和销毁端口,如何优雅的处理?

  24. albert
    120.197.154.*
    链接

    albert 2024-07-16 10:26:04

    这篇文章写得很好,我也出现了同样的问题,我也设置了TcpTimedWaitDelay=30,MaxUserPort=65535,但是,我的问题仍然会出现,服务器每隔十几天就出现这一错误,导致整个站点都无法使用,每次都需要重启服务器才行,我也不知还要怎么处理才能彻底解决问题了

发表回复

登录 / 登录并记住我 ,登陆后便可删除或修改已发表的评论 (请注意保留评论内容)

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

评论内容(大于5个字符):

  1. Your Name yyyy-MM-dd HH:mm:ss

使用Live Messenger联系我