原创 ohhahali 360云计算 2020-07-08
女主宣言
文本梳理了Octavia API接口访问慢问题的排查过程和解决方案,并对排查过程中涉及到的相关知识点进行了梳理,希望日后遇到类似的问题可以有所借鉴和参考。
PS:丰富的一线技术、多元化的表现形式,尽在“360云计算”,点关注哦!
Octavia API接口慢问题排查引发的思考
1
问题背景
Octavia是为openstack集群提供高可用的负载均衡解决方案,它对外提供REST API来创建业务访问的VIP,后端采用Haproxy+LVS来提供负载均衡服务,将业务请求自动分发给真实服务器。
在我们实际的使用中,Octavia对外提供的REST API接口的访问响应时间存在0.2s-50s之间的较大波动,造成VIP可能无法正常创建和查询。
Octavia服务架构
上图是我们Octavia服务使用的部署模式,通过keepalived vrrp实现高可用,haproxy后端挂载多个Octavia API服务节点。
2
问题排查过程
抓包
API接口访问慢,首先需要知道请求的时间具体消耗在哪个步骤。对接口的访问请求进行抓包,并观察请求过程中数据包的传输情况。由于接口请求较多,通过添加自定义头部信息对请求进行区分,并使用http header过滤抓包结果。发现时间消耗较长的请求,存在数据包重传现象。
分析
1)时间消耗
从抓包情况来看,client访问haproxy很快得到响应,时间消耗都在haproxy与octavia-api之间的交互,排除haproxy问题。
2)物理硬件/机器负载/网络抖动
查看服务器网卡不存在丢包和错包;haproxy与octavia-api都属于同一网段,也不存在网络抖动导致丢包;查看三台octavia-api机器负载不高,连接数等没有异常。
3)应用程序层面
从抓包现象上看,octavia-api没有响应syn或ack,查看9876端口连接情况。
netstat -s |grep -i listen #发现两个数值都在增长
1173805 times the listen queue of a socket overflowed
1175909 SYNs to LISTEN sockets dropped
增长的两个数值涉及到server端在连接建立过程中的两个队列:
半连接队列(syn queue):用来保存处于SYN_SENT和SYN_RECV状态的请求。
队列大小:max_qlen = 2^max_qlen_log(linux-3.10.0)
全连接队列(accept queue):用来保存处于established状态,但是应用层没有调用accept取走的请求。
队列大小:min(backlog, net.core.somaxconn)
当server端在建立连接过程中,收到client发出的syn或者ack包时,都要对accept队列是否溢出进行判断,溢出则会引起以上两个数值的增长。
部署环境接口压力并不大,为何会引起accept队列溢出呢?通过ss命令来查看当前server端对应accept队列的大小。
ss -ntlp|grep 9876
LISTEN 6 5 10.145.69.9:9876 *:* users:(("octavia-api",pid=10209,fd=4))
6和5分别表示Recv-Q和Send-Q,Recv-Q表示accept队列等待用户调用accept的完成3次握手的socket,Send-Q表示accept队列实际的大小。从数值看出,server端的accept队列确实存在溢出情况。
队列溢出后,server端如何处理此时收到的syn或者ack呢?由以下内核参数来决定:
/proc/sys/net/ipv4/tcp_abort_on_overflow
0表示直接丢弃ack,client会进行重传,重传次数根据内核参数tcp_synack_retries决定;1则直接发送rst将连接断开。
在测试环境将该值改为1后,请求被立即断开,证明重传server端队列溢出有关。
3
解决方案
确定是server端连接队列溢出导致重传后,需要修改accept队列的大小来解决。
第2部分中我们看到accept队列长度等于5,somaxconn默认为128,这说明octavia-api 服务端listen创建时传入的backlog值为5。该值过小,导致accept队列很容易就会溢出。
那么Octavia的backlog值来源于哪里呢?我们需要查看它代码的具体实现来找到答案。
octavia-api的启动逻辑中调⽤了wsgiref库⾥的simple_server.py中的make_server⽅法来创建⼀个WSGI server。具体逻辑如下:
simple_server.make_server(host, port, app) -> WSGIServer() -> server_bind -> BaseHTTPServer.HTTPServer.server_bind -> SocketServer.TCPServer.server_bind
图里看到SocketServer.TCPServer类中的server_activate方法listen函数传入的self.request_queue_size为固定值5,这就是octavia-api backlog值的来源。因此要改动该值,需要修改request_queue_size值的大小。
4
问题后续
经过一番排查确认并修改request_queue_size值大小后,查看octavia-api的backlog值已经为128。访问octavia-api发现数据包已无重传现象,accept队列无溢出,但访问速度仍然没有明显提升。
再次抓包发现连接正常,但octavia-api回包较慢,应该是应用层请求处理不过来导致。octavia-api只有单进程在处理请求,无法响应较多的接口调用。
结合openstack其他项目的处理方式,修改octavia-api WSGI server的创建方式。不再使用wsgiref库,改用openstack的oslo_service库来创建WSGI server,增加设置进程数的参数来设置octavia-api的进程个数(默认与cpu process个数相同),且oslo_service的WSGI server默认backlog值为128。经过此次修改后,接口响应速度提升至1s内。
另外,也可以使用httpd的mod_wsgi与Octavia配合部署提升其处理能力,毕竟wsgiref是官方给出的一个实现了WSGI标准用于演示用的简单Python内置库,并不建议在线上部署使用。