压力测试遭遇大量TIME_WITE之后

Posted 腾讯移动品质中心TMQ

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了压力测试遭遇大量TIME_WITE之后相关的知识,希望对你有一定的参考价值。


前语:http协议是互联网中最常使用的应用层协议,它的绝大多数实现是基于TCP协议的。

问题描述

某天,在对一个提供http接口的后台服务进行压力测试过程中,我们设定了几百qps(每秒请求数)开始测试几分钟后,请求一端(我们后续简称为:客户端)的压力结果统计日志中开始连续出现大量的报错信息:     

压力测试遭遇大量TIME_WITE之后图1-压力测试请求中出现大量报错

在压力测试前,根据之前的经验,同类服务的单机性能一般能够达到几千QPS,然而此时测试设定的压力值还不足200qps,这与预期存在1个数量级以上的性能差距,难道是被测服务存在问题么?

问题跟踪

为了确认被测服务的状态,我们首先登录了服务所在的机器,检查了服务资源的占用情况,结果是:CPU、内存、硬盘、I/O、网卡、fd、socket等各项资源都不存在较大负载。看来服务本身还远没有达到它的负载瓶颈。

在排除服务端问题后,我们重新分析了统计日志中的错误--"can not assign requested address",这是一个常见的socket的error,报错信息说明无法为socket创建新的连接,很可能是:tcp层的连接端口已经耗尽,无法为新的http请求分配端口建立连接。通过netstat命令,我们检查客户端,发现确实存在大量请求连接处于TIME_WAIT状态下:

压力测试遭遇大量TIME_WITE之后         图2-请求机器中tcp连接状态统计    

这里要说明一下,虽然理论上tcp连接可用端口号为0~65535--大约65536个,但是实际在不指定端口情况下连接服务时可用端口默认为32768~61000--大约只有28000多个,在linux系统中这个限制可以通过/proc/sys/net/ipv4/ip_local_port_range文件进行修改。    

我们知道http协议主要是基于tcp协议之上的,为了解决tcp层连接通道复用的问题,在http协议中通过header中的Connection字段定义了对于tcp长连接的支持:  

  • 在HTTP/1.0版本中,默认情况下在HTTP1.0中所有连接不被保持,如果客户端浏览器支持Keep-Alive,那么就在HTTP请求头中添加一个字段 Connection: Keep-Alive,当服务器收到附带有Connection: Keep-Alive的请求时,它也会在响应头中添加一个同样的字段来使用Keep-Alive。这样一来,客户端和服务器之间的HTTP连接就会被保持,当客户端发送另外一个请求时,就使用这条已经建立的连接通道。

  • 在HTTP/1.1版本中,默认情况下在HTTP1.1中所有连接都会被保持,除非在请求头或响应头中指明要关闭:Connection: Close,这也就是为什么Connection: Keep-Alive字段再没有意义的原因。

在压力测试过程中,我们模拟发送http请求的代码中使用的是http/1.1协议,应该会默认使用长连接,看来很可能是服务端不支持长连接,才会引起客户端频繁的创建TCP连接。通过tcpdump抓包,我们对此进行了证实:

压力测试遭遇大量TIME_WITE之后
图3-wireshark分析http响应信息

跟进分析

到此,我们发现服务端确实返回不支持长连接的信息(header中connection:close),导致客户端每次发起请求都会重新创建tcp通道。但是根据以往测试经验来看,比较常见的是在服务端出现大量time_wait状态的,那么为什么大量的time_wait状态会在客户端出现呢?

了解这个问题我们之前,可以先来看一下TCP正常连接建立和关闭连接时的状态变化图:

压力测试遭遇大量TIME_WITE之后
    
图4-tcp正常连接建立和终止所对应的状态图

上图是TCP"三次握手"和"四次挥手"的过程,相信很多读者都比较了解,下面我们来说说为什么要存在TIME_WAIT状态吧:

  • 可靠地实现TCP全双工连接的终止--TCP协议在关闭连接的四次挥手中,在主动关闭方发送的最后一个 ack(fin) ,有可能丢失,这时被动方会重新发fin, 如果这时主动方处于 CLOSED 状态 ,就会响应 rst 而不是 ack。所以主动方要处于 TIME_WAIT 状态,而不能是 CLOSED 。

明白了time_wait的存在原因和出现时机,可以得到一个结论:TIME_WAIT状态总是出现的主动关闭连接的一方,也就是说在我们压力测试过程中每次都是客户端主动关闭tcp连接的。从实际的抓包结果来看,确实如此:

压力测试遭遇大量TIME_WITE之后
图5-wireshark分析TCP关闭过程

但是我们实际遇到的多是time_wait出现在服务一端出现的,那么在http协议规定中,服务端返回connection:close的信息后,到底是应该由客户端还是服务端来主动关闭连接呢?

Connection: close 是一个 general-header( RFC 2616 - Hypertext Transfer Protocol -- HTTP/1.1 )即:既可以作为 request header 也可以作为 response header。Connection: close 的作用在于"协商(signal)"。在RFC2616  14.10 中:HTTP/1.1 defines the "close" connection option for the sender to signal that the connection will be closed after completion of the response.

通过RFC可以发现:请求和响应的双方都可以主动关闭TCP连接。

但是大多数的web Service实现是返回connection:close内容之后服务端会主动关闭连接。至于这样设计的原因,网上找到2个比较靠谱的解释:

  • server 主动关闭连接是历史原因:HTTP/0.9 协议中 response 是没有 header 的,所以 client 根本无从知道什么时候这个东西结束。

  • 在server 主动关闭连接的情况下,只要调用一次 close() 就可以释放连接,剩下的工作由内核 TCP 栈直接进行了处理,整个过程只有一次 syscall;如果是要求 client 关闭,则 server 在写完最后一个 response 之后需要把这个 socket 放入 readable 队列,调用 select / epoll 去等待事件;然后调用一次 read() 才能知道连接已经被关闭,这其中是两次 syscall,多一次用户态程序被激活执行,而且 socket 保持时间也会更长②;     

也许会有读者担心:如果客户端也不主动关闭TCP连接,服务端的socket资源会不会很快用完呢。这里留给读者们一个问题进行思考:在单个服务器上的服务端理论上能支持的最大TCP连接数是多少呢? 

解决方法

根据分析,我们知道了客户端请求报错的原因在于:服务端拒绝了客户端的HTTP长连接请求,同时服务端没有主动关闭tcp连接,而是由客户端主动关闭网络连接,导致在客户端出现大量time_wait,在压测进行到一段时候后由于没有新的socket端口可用而开始报错。

了解了原因后,解决方法就比较简单了,需要我们修改客户端所在linux环境下的tcp相关参数,编辑/etc/sysctl.conf文件,增加三行:

压力测试遭遇大量TIME_WITE之后
再执行以下命令,让修改结果立即生效即可:

/sbin/sysctl -p       #从配置文件“/etc/sysctl.conf”   加载内核参数设置

然后,我们的压力测试的客户端就不会再受time_wait问题困扰了。


压力测试遭遇大量TIME_WITE之后


参考资料:

① 《O'Reilly - HTTP - The Definitive Guide.pdf》


读者互动环节

在通过压力测试定位服务瓶颈的过程中,你遇到过哪些记忆深刻的问题,背后的原因如何?欢迎大家积极讨论!


精彩的回答除了可以移入精选留言,还有机会获取TMQ提供的精美礼品一份~ 期待您的回答哦~

长按指纹识别图中的二维码,获取更多测试干货分享!




  



以上是关于压力测试遭遇大量TIME_WITE之后的主要内容,如果未能解决你的问题,请参考以下文章

抑郁焦虑压力测试:内心恐惧测试,测测你内心最恐惧的是什么?

利用jmeter进行压力测试

网页压力测试工具

压力测试、负载测试和并发测试有啥区别?

性能测试机中存在大量的TIME_WAIT状态的连接,影响并发压力的发起

十步完成Web应用程序压力测试(转)