高併發場景下backlog詳解

參考:https://www.phpmianshi.com/?id=96

 

本文詳解高併發場景下backlog的配置和作用

 

環境介紹: PHP 7.3.5 +nginx/1.16.0 +Linux VM_0_15_centos 3.10.0-514.26.2.el7.x86_64

 

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的三次握手是存在很大的問題的。

驗證

  1. 查看全連接隊列(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

  2. 設置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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章