高并发场景下backlog详解

本文详解高并发场景下backlog的配置和作用

环境介绍: PHP 7.3.57 +nginx/1.16.0 +Linux CentOS Linux release 8.1.1911 (Core)

backlog定义:

已连接但未进行accept处理的SOCKET队列大小,并非syn的SOCKET队列。如果这个队列满了,将会发送一个ECONNREFUSED错误信息给到客户端,即 linux 头文件 /usr/include/asm-generic/errno.h中定义的“Connection refused”

在linux 2.2以前:

在底层维护一个由backlog指定大小的队列。服务端收到SYN后,返回一个SYN/ACK,并把连接放入队列中,此时这个连接的状态是SYN_RECEIVED。当客户端返回ACK后,此连接的状态变为ESTABLISHED。队列中只有ESTABLISHED状态的连接能够交由应用处理。第一种实现方式可以简单概括为:一个队列,两种状态。

linux 2.2以后:

在底层维护一个SYN_RECEIVED队列和一个ESTABLISHED队列,当SYN_RECEIVED队列中的连接返回ACK后,将被移动到ESTABLISHED队列中。backlog指的是ESTABLISHED队列的大小。SYN_RECEIVED队列的大小由/proc/sys/net/ipv4/tcp_max_syn_backlog系统参数指定,ESTABLISHED队列由backlog和/proc/sys/net/core/somaxconn中较小的指定。

当前最流行的DoS(拒绝服务)与DDos(分布式拒绝服务)的方式之一,这是一种利用TCP协议缺陷,导致服务器保持大量的SYN_RECV状态的“半链接”,并且会重试默认的5次回应第二个握手包,塞满TCP等待连接队列,耗尽资源(CPU满负载或内存不足),让正常的业务请求连接不进来。

TCP 3次握手
可分为4步

1 客户端发起connect(),发送SYN j
2 服务器从半链接队列(syn queue)中建立条目,响应SYN k, ACK J+1
3 客户端connect()成功返回,响应ACK K+1,服务器将socket从半链接队列(syn queue)移入全连接队列(accept queue),accept()成功返回
如何观察socket overflow 和 socket droped。

如果应用处理全连接队列(accept queue)过慢则会导致socket overflow

影响半连接队列(syn queue)溢出而导致socket dropped

[root@VM_0_15_centos ~]# netstat -s | grep -i listen
62678 times the listen queue of a socket overflowed
65640 SYNs to LISTEN sockets dropped
如果SYN socket overflow和socket droped急剧增加的话则说明,TCP的三次握手是存在很大的问题的。

验证

查看全连接队列(accept queue)溢出之后,OS处理设置:

0:表示如果三次握手第三步的时候全连接队列满了那么server扔掉client发过来的ack(在server端则会认为连接没有建立起来),server过一段时间再次发送syn+ack给client(也就是重新走握手的第二步),如果client超时等待比较短,client就很容易异常了。

1:表示如果三次握手第三步的时候全连接队列满了,server端就会发送一个reset包给client端,表示废弃这个握手过程和这个链接。(在server端也会认为连接没有建立起来)

cat /proc/sys/net/ipv4/tcp_abort_on_overflow

设置tcp_abort_on_overflow为1

echo 1 > /proc/sys/net/ipv4/tcp_abort_on_overflow

查看server端的web服务是否存在许多的connection reset peer错误。

在使用listen函数时,内核会根据传入参数的backlog跟系统配置参数/proc/sys/net/core/somaxconn中,二者取最小值,作为“ESTABLISHED状态之后,完成TCP连接,等待服务程序ACCEPT”的队列大小。在kernel 2.4.25之前,是写死在代码常量SOMAXCONN,默认值是128。在kernel 2.4.25之后,在配置文件/proc/sys/net/core/somaxconn (即 /etc/sysctl.conf 之类 )中可以修改。

[root@VM_0_15_centos ~]# cat /proc/sys/net/core/somaxconn
32768
[root@VM_0_15_centos ~]# cat /usr/local/php/etc/php-fpm.conf |grep backlog
listen.backlog = 1024
[root@VM_0_15_centos ~]# ss -ln |grep -E ‘php|Netid’
Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port
u_str LISTEN 0 1024 /dev/shm/php-cgi.sock 79785144 * 0

可见: 内核会根据传入参数的backlog跟系统配置参数/proc/sys/net/core/somaxconn中,二者取最小值

我这里php-fpm 配置的 listen = /dev/shm/php-cgi.sock ,如果你配置的 tcp监听9000,应该用如下命令验证

[root@VM_0_15_centos ~]# ss -lt
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 1024 0.0.0.0:9000 :

backlog大小设置为多少合适?

1、backlog太大了,导致php-fpm处理不过来,nginx那边等待超时,断开连接,报504 gateway timeout错。同时php-fpm处理完准备write 数据给nginx时,发现TCP连接断开了,报“Broken pipe”。

2、php-fpm的backlog太小的话,nginx之类的client请求,根本进入不了php-fpm的accept queue,报“502 Bad Gateway”错。所以,这还得去根据php-fpm的QPS来决定backlog的大小。计算方式最好为QPS=backlog。建议设置在1024以上,最好是2的幂值(因为内核会调整成2的n次幂)

SYN queue长度由tcp_max_syn_backlog指定,accept queue则由net.core.somaxconn决定,listen(fd, backlog)的backlog上限由somaxconn决定.

php-fpm下backlog配置

php-fpm.conf进行配置

listen.backlog = 1024 默认值是 511 ,是在2014年7月22日修改的, Set FPM_BACKLOG_DEFAULT to 511

其中理由是“backlog值为65535太大了。会导致前面的nginx(或者其他客户端)超时”,假设FPM的QPS为5000,那么65535个请求全部处理完需要13s的样子。但nginx(或其他客户端)已经等待超时,关闭了这个连接。当FPM处理完之后,再往这个SOCKET ID 写数据时,却发现连接已关闭,得到的是“error: Broken Pipe”,在nginx、redis、apache里,默认的backlog值都是511。故这里也建议改为511。

nginx下backlog配置

/etc/nginx/nginx.conf进行配置

listen 80 backlog=8192; # 默认为511

linux下backlog配置

/etc/sysctl.conf 进行配置

net.core.somaxconn = 1048576 # 默认为128
net.core.netdev_max_backlog = 1048576 # 默认为1000
net.ipv4.tcp_max_syn_backlog = 1048576 # 默认为1024

猜你喜欢

转载自blog.csdn.net/songxi_bo/article/details/107960543