记录一下测试过程中遇到的问题
背景
被测试HTTP服务在容器中运行,使用的是gunicorn,在另外一台server通过python requests做压力测试
问题1. urllib3 connection pool full
urllib3.connectionpool Connection pool is full
requests使用了urlib3,urllib3中有PoolManager,它会复用连接,所以如果压测过程中,大量发起requests,会导致PoolManager中的connection pool满掉,进而出现这个问题。
【解决方案】
根据你的量适当调整pool_connections的值
session = requests.session()
adapter = requests.adapters.HTTPAdapter(pool_connections=1000, pool_maxsize=4000)
session.mount("http://", adapter)
问题2.Too many open files
Failed to establish a new connection: [Errno 24] Too many open files
每一个连接会打开一个socket,一个socket会使用一个句柄,可以通过
ulimit -n
查看当前系统的open file
默认是1024
在做压力测试的机器上,需要修改该值
【解决方案】
ulimit -n 65535
注:不过这个值只是在当前session中做了修改,如果下次登陆时还需要再做修改;
问题3 requests.exceptions.ConnectTimeout
客户端大量连接出现Connect timeout错误
原因有两个:
- 客户端的connect time设置时间过短
- 服务端的syn backlog设置过小
【解决方案】
客户端的connect time设置,这里主要是requests库里配置超时的地方注意一下
requests.post(url, data=data, headers=headers, timeout=(CONNECTION_TIMEOUT, REQUEST_TIMEOUT))
服务端有2个参数可以配置
- netdev_max_backlog 位置/proc/sys/net/core/netdev_max_backlog,主要控制当kernel无法及时处理时接收到的packets的队列大小
- tcp_max_syn_backlog 位置/proc/sys/net/ipv4/tcp_max_syn_backlog,tcp协议栈在收到客户端发送的SYN消息后会回复SYNACK同时将此消息放入队列,等待客户端发送ACK确认
问题4 requests.exceptions.ConnectionError:Connection reset by peer
'Connection reset by peer'
在客户端,大量的tcp连接被reset了,这里我们需要检测被测试系统的tcp backlog值是否足够,如果不够,服务连接达到瓶颈时,可能会出现该问题。
【解决方案】
tcp的backlog主要有2个地方配置
- listen backlog 应用服务的tcp listen时,有一个参数是backlog,作为服务端,这个值不可以过小,请根据服务器物理性能适当设置
- somaxconn 位置/proc/sys/net/core/somaxconn,控制ESTABLISHED的接连数量。 一些系统中默认是128,作为服务器显然是不足的,需要往上调整。 注意,如果不调整somaxconn,仅调整listen函数中的backlog,最终的结果是无效的。 比如listen函数中的backlog中设置1024,但是默认的somaxconn=128,实际上还是128.
问题5 Possible SYN flooding
通过
dmesg -T
查看系统消息时,如果有如下消息
TCP: request_sock_TCP: Possible SYN flooding on port xxx. Sending cookies
是大量SYN消息收到了,存入了SYN-ACK队列,但是没有被处理。
这可能是因为tcp的backlog设置过小,或者服务器处理性能不足导致的;对于前者请参考问题4解决方案,后者请优化服务性能。
问题6 requests.exceptions.BrokenPipeError
压测客户端出现如下报错
[Errno 32] Broken pipe
查阅资源是当往一个已经close的socket写时,会收到SIGPIPE。
This might be happening when a client program doesn’t wait till all the data from the server is received and simply closes a socket (using close function).
产生这个问题的原因是,我在线程中post数据,但是再压测程序最后的主进程中,在线程socket未结束的时候,直接close了socket。
其他注意事项
在docker以镜像方式部署的时候,请检查一下所用镜像中的系统配置,
比如:
open files是否是默认的1024?这个值肯定是过小的;
上述几个backlog参数是否是默认值?
一般镜像中的系统配置是只读的,需要在docker run的时候通过携带参数的方式来修改
比如修改somaxconn和tcp_max_syn_backlog的方式如下
docker run
--sysctl net.core.somaxconn=2048
--sysctl net.ipv4.tcp_max_syn_backlog=4000
如果是使用docker-compose方式来启动的可以在yaml文件中添加如下
sysctls:
net.core.somaxconn: 2048
net.ipv4.tcp_max_syn_backlog:4000
或者
sysctls:
- net.core.somaxconn=2048
- net.ipv4.tcp_max_syn_backlog=4000
注:
- netdev_max_backlog参数在容器中是没有的,只能修改宿主机配置
- 容器里的参数与宿主机的参数不冲突,如果两者不一致以镜像中的参数为实际运行结果
检测tcp状态
可以通过脚本实时检测tcp的状态变化
下面是我写的脚本,用于检测服务端口,这里我的服务端口是5000
脚本 print_tcp_conn_stat.sh, 内容如下,
#!/bin/bashecho"TIME_WAIT :"`netstat -tuna |grep5000|grep"TIME_WAIT"|wc -l`echo"ESTABLISH :"`netstat -tuna |grep5000|grep"ESTABLISH"|wc -l`echo"CLOSE_WAIT :"`netstat -tuna |grep5000|grep"CLOSE_WAIT"|wc -l`echo"SYN_SENT :"`netstat -tuna |grep5000|grep"SYN_SENT"|wc -l`echo"SYN_RECV :"`netstat -tuna |grep5000|grep"SYN_RECV"|wc -l`
然后使用watch命令,来做实时监控,
# 每1s监控一次watch -d -n 1 ./print_tcp_conn_stat.sh
效果如下,
这几个状态说明一下:
- TIME_WAIT 客户端结束时,socket的状态
- ESTABLISH tcp连接建立后的状态
- CLOSE_WAIT 服务端结束时,socket的状态
- SYN_SENT 客户端发送SYN
- SYN_RECV 服务端接收SYN
使用keepalive优化服务能力
如果你也使用gunicorn,可以在启动gunicorn的命令中添加如下参数,
gunicorn
--keep-alive 30
使用了keepalive之前,每一次tcp访问,都会经理SYN_SENT -> ESTABLISH -> CLOSE_WAIT 过程
(因为我客户端也复用了连接,所以没有TIME_WAIT)
使用keepalive后,服务器建立连接后,状态会一直保持在ESTABLISH,直到keepalive的timeout到了之后,服务端才结束连接,出现CLOSE_WAIT
版权归原作者 安安爸Chris 所有, 如有侵权,请联系我们删除。