01
背景
公司投标需要各个性能评测点都通过测试。但其中有一个评测点:1000个并发下载80k大小的文件需要1s内响应,因性能测试工具Loadrunner的测试结果上最大响应时间总是5.6s+的而一直没有通过。
测试环境中的服务端是一台Nginx,它负责代理转发客户端(由Loadrunner模拟)的请求转发给三台应用服务器进行处理,并负责负载均衡。
02
实践
Nginx,Loadrunner已是最优的服务器资源配置,它们也同属一个局域网的两台机器。从负载时的资源上看,服务器Load低,Cpu、内存、磁盘i/o,系统、网络负载并无瓶颈。
此时Loadrunner的Analysis 工具明确指出:整个请求和响应链路上网络时间占了很大部分,服务器处理时间也在1s左右,所以要达到整个链路到1s以下,可谓困难重重。
探索式方法:
A.在缩小网络时间的方法上我们尝试将测试机和Nginx服务器(虚拟机)部署到一台主机,也修改网卡配置,以提高网络传输的吞吐量。
B.在服务器端上,我优化了Nginx相关的性能配置,如提高并发连接的处理数,保持长连接等,如下:
开发也对Nginx启用了缓存,使得并发请求时以提高缓存命中率,从而减少服务器的处理时间。
可惜,这些方法都未使得效率达到飞跃性的提升。
直接对网络问题进行分析与定位:
考虑整体到局部的方式对问题进行拆解。
A:Anlysis的结果摘要分析系统的总体性能,整个网络通讯过程时间=DNS解析+发送到webserver的初始连接时间+服务端处理至接收第一个报文包的时间+接收完整报文时间+客户端的延迟。
Loadruner分析图上显示发送到webserver的初始连接时间占比很大,超过一半的整体链路时间。
连接时间占比大,基于过往的经验,首先想到了TCP连接队列溢出,果然系统日志里发现TCP三次握手syn待连接过程存在丢包造成的重传现象。
调整tcp半连接池丢包之后按同样的并发策略(同样有足够的休眠)重新测试,连接时间的占比变小,响应时间也降到3s+。
B:Max响应时间3s+还是未达到标准,在通过在服务端tcpdump抓包+Wireshark分析后,我明确了性能的瓶颈点一定是在服务端上。
在数据传输阶段,一个tcp流的全局视角:
211(Nginx服务端)发送存在多次重传,并在206(Loadrunner端)接收窗口(绿线)还未满时,就停止发送报文包,大约卡了0.22s,黄色线一上升,蓝线就上升,(ack被确认,就开始继续发)
图上发送暂停代表服务端的TCP发送缓冲区已满,导致buffer不能释放,新的发送内容加不进来。
对报文流片段进行分析:206(Loadrunner端)连续6次反馈没有接收到seq 45434分组包,211(Nginx端)进行了超时重传和快速重传,说明数据存在丢包,并且因为频繁丢包造成了报文乱序,导致重发并带来了通讯时延。
因此,可以排除网络时延和206(Loadrunner端)的原因,把问题指向给服务端。
继续探索丢包原因,通过分析服务端接收数据包的过程:数据接收从网卡开始 ,再经由网卡驱动交给内核网络协议栈中由底向上进行操作:数据链路层由网络驱动接口→网络层、传输层:由内核处理TCP/IP协议栈—会话层:socket →再到应用处理。
发送过程:为了避免上下游收发速度不等带来的丢包,每个环节都有设置缓冲区,起到缓存、排队、流量控制等作用。因此若各个缓冲区不够,则存在丢包的问题。
排除了网络驱动接口的ring buffer queue和CPU核backlog队列丢包后,通过监视内核的SNMP计数器确实查到了
TcpExtTCPReqQFullDrop
和 TcpExtTCPBacklogDrop丢包数一直在增加。即TcpBacklog和接收缓冲区满带来的丢包:
由于内核判断是否可写入的函数
sk_stream_is_writeable并不仅仅是查看有无剩余发送空间,而是需要满足剩余发送缓存空间大于等于已用发送空间的一半,而已用发送空间是包括已发送待ack应答的数据。
因为上游应用发太快(Nginx加了缓存无需转发下游),而数据需要经过各个网络协议栈封装到网卡→网络→对方网卡等多个过程,势必会比应用处理慢,并且下载业务由于响应的是个80k文件二进制流将频繁调用socket输出数据 。
最终造成已用发送空间增长迅速,无法继续发出数据。因此需要调整应用发送的频次。
并且应用发送频次的增加同时也增加了socket处于用户态的时间,相关的内核处理代码提到:当socket正忙时,接收的数据包暂存在内核接收数据包的缓存队列上,即sk_backlog 接收队列,backlog长度不够的时候则drop,因此产生了TcpBacklogddrop。
因为接收缓冲区包括
sk_backlog ,sk_receive_queue等队列的所有段的数据总长度,所以也会导致接收缓冲区满而丢包的错误。
如下图wireshark分析显示:211(Nginx端)的TCP Window Full,即接收窗口满
因为TCP的流量控制功能,当Nginx的接收窗口满,此时loadrunner会停止向Nginx传递数据,直到Nginx可用窗口变成非 0。
综上,Nginx应用响应时调用socket发送频次频繁是根因。需要先控制Nginx应用响应的发送频次,再根据效果适当增加sock发送缓冲区和接收缓冲区以及整体缓冲区的大小。
1. 解决tcp半连接syn队列不够造成的丢包:
半连接队列受内核参数
tcp_max_syn_backlog,somaxconn 和应用服务器的backlog 长度影响:
当 tcp_max_syn_backlog
> min(somaxconn, backlog) 时, 半连接队列最大值 max_qlen_log = min(somaxconn, backlog) * 2;
当 tcp_max_syn_backlog
< min(somaxconn, backlog) 时, 半连接队列最大值 max_qlen_log = tcp_max_syn_backlog * 2;
即tcp半连接syn队列实际长度
等于tcp_max_syn_backlog,somaxconn 和应用服务器的backlog最小值的两倍,
由于Nginx的 backlog默认512太小,故增加应用的backlog 到8096:
Nginx.conf增加backlog参数
net.core.somaxconn 默认是128,故增加到8095:
sysctl -w net.core.somaxconn=8095
tcp_max_syn_backlog默认是1024,故增加至8097后未再发现半连接syn队列溢出:
sysctl -w net.ipv4.tcp_max_syn_backlog=8097
2. Nginx调整应用响应时调用socket发送频次:
Nginx设置每个tcp连接的缓冲区,缓冲区功能是可暂存后端的响应,接收完整的响应后再将其发送给客户端,减少socket操作频次和即时带宽。
如果缓冲功能被禁用,Nginx只会默认为一个内存页大小的缓冲区内缓存服务器响应的开头部分后就开始向客户端传输。
配置方法:
Nginx.conf在http节点添加:
proxy_buffer_size 16k;//响应头大小
proxy_buffers 4 81k; //为每个请求/连接设置的缓冲区数量和大小,默认只有 4 4k/8k
proxy_buffering on;//启用缓冲
3. 解决TCPReqQFullDrop:
调大TCP接收缓冲区大小:
sysctl -w net.core.rmem_default = 212992
sysctl -w net.core.rmem_max = 212992
4. 调大TCP发送缓冲区大小:
sysctl -w net.ipv4.tcp_wmem = 4096 16384 4194304
5. 调大总缓冲区大小:
sysctl -w net.ipv4.tcp_mem = 765006 1020008 1530012
需要注意:sysctl -w 修改的参数,在服务器重启后会失效,如果要永久生效需要vi /etc/sysctl.conf后使用sysctl -p 保存。
经过以上调整,Max响应时间和平均响应时间均下降至1s以下
03
总结
以上内容是按一定的分析逻辑进行了体系化的调整,探索过程实际却是磕磕盼盼。但庆幸的是,本来已经穷途末路,只能开启loadrunner日志再去抓包分析请求响应过程的无奈之举,竟意外得到性能的提升;因正常情况下开日志是会拖慢效率的,反逻辑的结果让我确定地排除了中间的网络硬件资源限制的因素,让我有希望地把目光投向了服务端的资源限制上。
最后通过抓包专研网络通讯,也经过分层分析,最终解决了本次大负载下的网络性能问题。
别走开,每日互动“留言、点赞、在看、分享、关注”将有机会领取定制款“鼠标垫”啦!(每周互动奖励,综合以上这5个方面,挑选互动最多的2位小伙伴各获得一个鼠标垫)
PS:每周一统计上一周(周一至周日)互动情况,并于当天17:30公布获奖名单及领取方式。
图:定制款鼠标垫
今日互动话题:
面对高并发请求时,除了Nginx,你还使用过哪些技术或工具?
上周获奖名单公布:
领取方式:
获奖的同学,扫码加小编微信(备注:周互动领奖),领取奖品!截止日期是2024年11月22日(周五)上午12点,超过期限未与我们联系,则视为弃权。
......
本文为51Testing软件测试网
第八十期51测试天地内容
剩余精彩内容请点击下方
阅读原文 查看