情景描述:
前一阵,我碰到个关于并发测试的离奇问题:一个客户用我们的产品运行并发测试(10000个并发用户),在测试前期(< 4000 用户)测试运行良好,然而每次当并发量接近4000的时候,给服务器端的压力就再也上不去了(如下图一),更离奇的是此时的Load Agent机器的CPU和内存都还没有饱和(如下图二),但有固定数目的并发用户开始出现连接异常(Error 12029:Error_Internet_Cannot_Connect & Error12002:Error_Internet_Timeout).
定位问题:
显然瓶颈并不是CPU和Memory,而应该是Network这里。我首先想到的是看netstat的日志。令人惊讶的是,日志里有15000个TCP连接,而其中20%是CLOSE_WAIT状态,50%是TIME_WAIT状态,只有30%是ESTABLISHED状态。也就是说仅仅4000个左右的连接是真正工作状态的。(参考如下的Socket状态图)
寻找原因:
仔细分析一下可能引起这个问题的原因,我找了些相关的资料,大概有这些可能性:
1) 很多端口处于'TIME_WAIT'状态,这些端口不能被利用起来处理request,而且他们会长期占据端口(要等2MSL的时间),而windows2008操作系统默认动态分配的端口数只有15000。
2) 大量端口处于‘CLOSE_WAIT'状态,即半关闭状态。这种一般是服务器发起关闭请求(FIN),而客户端没有及时回收这些port引起的。 为什么服务器会发起关闭请求呢?有一个可能性就是这个socket连接闲置太久了,而在服务器端是有idle超时处理的。在java里需要设置SO_LINGER参数(参考‘Connection Stuck In CLOSE_WAIT’)
3) 大量的并发用户(10000)需要大量的分配端口以供load agent驱使。所以有可能要手动设置‘MaxUserPort' (不同的操作系统设置不一样,这里仅供参考windows2008)。
4) 大量的端口如果不能被重用,则结果是对端口数的需求剧增。通常性能测试工具(LR & OATS)应该有设定选择重用连接(如Preserve connections between iterations 和 Enable Keep-Alive)
可用的方案:
针对以上可能的原因,我们要相应提供解决方案:
1) 缩短TCPWindowSize值和主动迅速清空'TIME_WAIT' 端口。(参考TCP TIME-WAIT Delay)
2) 在Load Agent端,要及时关闭服务器发起的关闭连接('CLOSE_WAIT' 状态的连接) (目前我还不知道如何在java里搞定这事情)
3)扩大操作系统动态端口的分配范围(Windows2008上默认是49152 - 65534),用’netsh‘命令可以更改(参考'Support Microsoft')
4)避免连接不能被重用的情况,在该案例中,很多连接不能被重用的原因很可能是该连接被闲置太久了(think time太长或者response时间太长),所以应该尝试缩短navigation和navigation之间的think time或监控服务器的响应时间。
命中答案:
经过反复试验,通过调试单个虚拟用户情况下运行脚本,终于找到了原因出在‘think time'上。
A) 观察wireshark的capture,发现某个请求隔了60秒没有响应,如下图:
B)服务器其实半途发了个FIN请求,但Load Agent没有及时ACK并关闭该连接,该连接处于’CLOSE_WAIT‘状态。
结论:
1) 在并发测试中要注意端口和连接的回收和重用。
2) 服务器响应时间是一个很重要的衡量性能的KPI,不要忽视,并且严格限定他的最大值。(该案例中响应时间过长会导致连接闲置并处于半关闭状态)
3)适当根据并发量的大小调整操作系统的可分配端口数。该案例如果是10000个并发用户,并且是向同一个server发送请求,那么尽量把端口数控制在12000到15000左右。 而有些测试场景是跨域请求多个服务器(如SSO),则每个并发用户至少需要2个以上的端口(这样做既避免端口分配不足,又有利于重用连接,减少DNS和Connect时间)