现象
业务收到一波急促的报警,第一眼看到的是大量curl第三方请求失败。响应ERROR_NO=7,追查一波发现异常日志大量集中在某一个业务节点,异常时节点CPU负载升高,从一些细节上看不像是网络链路或解析异常
手动curl -v ‘http://xxxx’ 能偶发复现得到下图的结果
排查
群里有个位司机提示此问题看上去比较像是端口耗尽,于是往端口线索追查
$ cat /proc/sys/net/ipv4/ip_local_port_range 32768 60999
看上去只开放了2万多个可用连接数(连接数的理解可以参见这位做的测试,比较简单明了 https://mozillazg.com/2019/05/linux-what-net.ipv4.ip_local_port_range-effect-or-mean.html)
The /proc/sys/net/ipv4/ip_local_port_range defines the local port range that is used by TCP and UDP traffic to choose the local port. You will see in the parameters of this file two numbers: The first number is the first local port allowed for TCP and UDP traffic on the server, the second is the last local port number. For high-usage systems you may change its default parameters to 32768-61000 -first-last.
使用 netstat -antp | grep TIME_WAIT | wc -l
发现待释放数量非常多,很有可能短时间耗尽可用连接数
(注意,这里只grep了TIME_WAIT,实际上ESTABLISHED状态数量也可能很多,实践时也要考虑进去)
使用 netstat -antp | grep TIME_WAIT |awk '{print $5}'|sort | uniq -c | sort -k1 -rn | head -n 20
看下哪些连接使用了大量待释放连接
用 tcpdump -A -i eth0 dst ***.11.76
查看第一条报文内容,很容易就发现是在调用哪个服务
解决
业务层面
改为长连接
定位到某些时段有部分高频接口调用,改为长连接,可以可以省下重复建连的时间
强制声明短连接
如果在请求头上未声明Connection:,则多数sever会默认为长连接,等待客户端主动socket_close(),即client先发F包
所以,如果是php-fpm这种无法实现跨请求维护curl长连接的,可以试着在curl头带上Connection:close 强制声明为短连接。多数server侧的实现都会在收到此参数时,响应完body后主动发起socket_close(),即先发F包
从而把time_wait问题留在server侧,因为对server来说,dst客户端的host和port都是不同的,在TCP四元组机制下,server侧不会出现端口消尽问题,又因为一般server系统的fd数量会开得非常大,可以是千万级别,所以基本不会出现问题(但还没测试过大量fd在time_wait时系统性能会不会下降太厉害)
声明ADDR可复用(待实践)
使用socket系列函数封装一份curl实现,关键之处在于加上这么一个参数
socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, 1)
这种比较适合curl业务比较简单的场景,毕竟自己完善一套健壮的curl套件不是容易的事
未经验证的demo,仅参考
<?php $service_port = 9090; $address = '127.0.0.1'; $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); if ($socket === false) { echo "socket_create() failed: reason: " . socket_strerror(socket_last_error()) . "\n"; } $result = socket_connect($socket, $address, $service_port); if ($result === false) { echo "socket_connect() failed.\nReason: ($result) " . socket_strerror(socket_last_error($socket)) . "\n"; } $data = 'data={"id":1}'; $in = "POST /helloworld/say HTTP/1.1\r\n"; $in .= "Host: localhost\r\n"; $in .= "Content-Type: application/x-www-form-urlencoded\r\n"; $in .= "Content-Length: ".strlen($data)."\r\n"; //$in .= "Connection: Close\r\n"; $in .= "\r\n"; $in .= $data."\r\n\r\n"; socket_write($socket, $in, strlen($in)); $data = ''; while ($out = socket_read($socket, 2048)) { $data.= $out; } socket_close($socket);
系统层面(待实践)
是由于linux分配的客户端连接端口用尽,无法建立socket连接所致,虽然socket正常关闭,但是端口不是立即释放,而是处于TIME_WAIT状态,默认等待60s后才释放。 可能解决方法
调低time_wait状态端口等待时间:
1. 调低端口释放后的等待时间,默认为60s,修改为15~30s sysctl -w net.ipv4.tcp_fin_timeout=30
2. 修改tcp/ip协议配置, 通过配置/proc/sys/net/ipv4/tcp_tw_resue, 默认为0,修改为1,释放TIME_WAIT端口给新连接使用 sysctl -w net.ipv4.tcp_timestamps=1
3. 修改tcp/ip协议配置,快速回收socket资源,默认为0,修改为1 sysctl -w net.ipv4.tcp_tw_recycle=1
增加可用端口:
$ vi /etc/sysctl.conf net.ipv4.ip_local_port_range = 10000 65000
近期评论