Tomcat中的backlog參數


  在linux 2.2以前,backlog大小包括了半連接狀態和全連接狀態兩種隊列大小。linux 2.2以后,分離為兩個backlog來分別限制半連接SYN_RCVD狀態的未完成連接隊列大小跟全連接ESTABLISHED狀態的已完成連接隊列大小。互聯網上常見的TCP SYN FLOOD惡意DOS攻擊方式就是用/proc/sys/net/ipv4/tcp_max_syn_backlog來控制的。在使用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 之類 )中可以修改。我稍微整理了流程圖,如下: 

tcp-sync-queue-and-accept-queue-small 
  如圖,服務端收到客戶端的syn請求后,將這個請求放入syns queue中,然后服務器端回復syn+ack給客戶端,等收到客戶端的ack后,將此連接放入accept queue。大約了解其參數代表意義之后,我稍微測試了一番,並抓去了部分數據,首先確認系統默認參數

root@vmware-cnxct:/home/cfc4n# cat /proc/sys/net/core/somaxconn

root@vmware-cnxct:/home/cfc4n# ss -lt
State      Recv-Q Send-Q         Local Address:Port                    Peer Address:Port
LISTEN     0      128                        *:ssh                           *:*
LISTEN     0      128                 0.0.0.0:9000                           *:*
LISTEN     0      128                       *:http                           *:*
LISTEN     0      128                       :::ssh                           :::*
LISTEN     0      128                      :::http                           :::*

在FPM的配置中,listen.backlog值默認為511,而如上結果中看到的Send-Q卻是128,可見確實是以/proc/sys/net/core/somaxconn跟listen參數的最小值作為backlog的值。

cfc4n@cnxct:~$ ab -n 10000 -c 300 http://172.16.218.128/3.php
This is ApacheBench, Version 2.3 <$Revision: 1604373 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 172.16.218.128 (be patient)
Completed 1000 requests
Completed 2000 requests
Completed 3000 requests
Completed 4000 requests
Completed 5000 requests
Completed 6000 requests
Completed 7000 requests
Completed 8000 requests
Completed 9000 requests
Completed 10000 requests
Finished 10000 requests


Server Software:        nginx/1.4.6
Server Hostname:        172.16.218.128
Server Port:            80

Document Path:          /3.php
Document Length:        55757 bytes

Concurrency Level:      300
Time taken for tests:   96.503 seconds
Complete requests:      10000
Failed requests:        7405
   (Connect: 0, Receive: 0, Length: 7405, Exceptions: 0)
Non-2xx responses:      271
Total transferred:      544236003 bytes
HTML transferred:       542499372 bytes
Requests per second:    103.62 [#/sec] (mean)
Time per request:       2895.097 [ms] (mean)
Time per request:       9.650 [ms] (mean, across all concurrent requests)
Transfer rate:          5507.38 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    9  96.7      0    1147
Processing:     8 2147 6139.2    981   60363
Waiting:        8 2137 6140.1    970   60363
Total:          8 2156 6162.8    981   61179

Percentage of the requests served within a certain time (ms)
%    981
%   1074
%   1192
%   1283
%   2578
%   5352
%  13534
%  42346
%  61179 (longest request)

  apache ab這邊的結果中,非2xx的http響應有271個,在NGINX日志數據如下:

root@vmware-cnxct:/var/log/nginx# cat grep.error.log |wc -l

root@vmware-cnxct:/var/log/nginx# cat grep.access.log |wc -l
0
root@vmware-cnxct:/var/log/nginx# cat grep.access.log |awk '{print $9}'|sort|uniq -c
 200
 502
 504
root@vmware-cnxct:/var/log/nginx# cat grep.error.log |awk '{print $8  $9  $10 $11}'|sort |uniq -c
 (111: Connection refused) while
 out (110: Connection timed

  從nginx結果中看出,本次壓測總請求數為10000。http 200響應數量9729個;http 502 響應數量186個;http 504響應數量未85個;即非2xx響應總數為502+504總數,為271個。同時,也跟error.log中數據吻合。同時,也跟TCP數據包中的RST包數量吻合。 
tcp.connection.rst-271

  在nginx error中,錯誤號為111,錯誤信息為“Connection refused”的有186條,對應着所有http 502響應錯誤的請求;錯誤號為110,錯誤信息為“Connection timed out”的有85條,對應着所有http 504響應錯誤的請求。在linux errno.h頭文件定義中,錯誤號111對應着ECONNREFUSED;錯誤號110對應着ETIMEDOUT。linux man手冊里,對listen參數的說明中,也提到,若client連不上server時,會報告ECONNREFUSED的錯。

Nginx error日志中的詳細錯誤如下:

 //backlog 過大,fpm處理不過來,導致隊列等待時間超過NGINX的proxy 4#0: *24135 upstream timed out (110: Connection timed out) while connecting to upstream, client: 172.16.218.1, server: localhost, request: "GET /3.php HTTP/1.0", upstream: "fastcgi://192.168.122.66:9999", host: "172.16.218.128" //backlog 過小 [error] 54416#0: *38728 connect() failed (111: Connection refused) while connecting to upstream, client: 172.16.218.1, server: localhost, request: "GET /3.php HTTP/1.0", upstream: "fastcgi://192.168.122.66:9999", host: "172.16.218.128"

  在壓測的時候,我用tcpdump抓了通訊包,配合通信包的數據,也可以看出,當backlog為某128時,accept queue隊列塞滿后,TCP建立的三次握手完成,連接進入ESTABLISHED狀態,客戶端(nginx)發送給PHP-FPM的數據,FPM處理不過來,沒有調用accept將其從accept quque隊列取出時,那么就沒有ACK包返回給客戶端nginx,nginx那邊根據TCP 重傳機制會再次發從嘗試…報了“111: Connection refused”錯。當SYNS QUEUE滿了時,TCPDUMP的結果如下,不停重傳SYN包。 
tcp-sync-queue-overflow 
  對於已經調用accept函數,從accept queue取出,讀取其數據的TCP連接,由於FPM本身處理較慢,以至於NGINX等待時間過久,直接終止了該fastcgi請求,返回“110: Connection timed out”。當FPM處理完成后,往FD里寫數據時,發現前端的nginx已經斷開連接了,就報了“Write broken pipe”。當ACCEPT QUEUE滿了時,TCPDUMP的結果如下,不停重傳PSH SCK包。(別問我TCP RTO重傳的機制,太復雜了,太深奧了 、 TCP的定時器系列 — 超時重傳定時器 ) 
tcp-accept-queue-overflow 
對於這些結論,我嘗試搜了很多資料,后來在360公司的「基礎架構快報」中也看到了他們的研究資料《 TCP三次握手之backlog 》,也驗證了我的結論。

關於ACCEPT QUEUE滿了之后的表現問題,早上 IM鑫爺 給我指出幾個錯誤,感謝批評及指導,在這里,我把這個問題再詳細描述一下。如上圖所示

  • NO.515 client發SYN到server,我的seq是0,消息包內容長度為0. (這里的seq並非真正的0,而是wireshark為了顯示更好閱讀,使用了Relative SeqNum相對序號)
  • NO.516 server回SYN ACK給client,我的seq是0,消息包內容長度是0,已經收到你發的seq 1 之前的TCP包。(請發后面的)
  • NO.641 client發ACK給server,我是seq 1 ,消息包內容長度是0,已經收到你發的seq 1 之前的TCP包。
  • NO.992 client發PSH給server,我是seq 1 ,消息包內容長度是496,已經收到你發的seq 1 之前的TCP包。
  • ………..等了一段時間之后(這里約0.2s左右)
  • NO.4796 client沒等到對方的ACK包,開始TCP retransmission這個包,我是seq 1,消息包長度496,已經收到你發的seq 1 之前的TCP包。
  • ……….又…等了一段時間
  • NO.9669 client還是沒等到對方的ACK包,又開始TCP retransmission這個包,我是seq 1,消息包長度496,已經收到你發的seq 1 之前的TCP包。
  • NO.13434 server發了SYN ACK給client,這里是tcp spurious retransmission 偽重傳,我的seq是0,消息包內容長度是0,已經收到你發的seq 1 之前的TCP包。距離其上次發包給client是NO.516 已1秒左右了,因為沒有收到NO.641 包ACK。這時,client收到過server的SYN,ACK包,將此TCP 連接狀態改為ESTABLISHED,而server那邊沒有收到client的ACK包,則其TCP連接狀態是SYN_RCVD狀態。(感謝IM鑫爺指正)也可能是因為accept queue滿了,暫時不能將此TCP連接從syns queue拉到accept queue,導致這情況,這需要翻閱內核源碼才能確認。
  • NO.13467 client發TCP DUP ACK包給server,其實是重發了N0.641 ,只是seq變化了,因為要包括它之前發送過的seq的序列號總和。即..我的seq 497 ,消息包內容長度是0,已經收到你發的seq 1 之前的TCP包。
  • NO.16573 client繼續重新發消息數據給server,包的內容還是NO.992的內容,因為之前發的幾次,都沒收到確認。
  • NO.25813 client繼續重新發消息數據給server,包的內容還還是NO.992的內容,仍沒收到確認。(參見下圖中綠色框內標識)
  • NO.29733 server又重復了NO.13434包的流程,原因也一樣,參見NO.13434包注釋
  • NO.29765 client只好跟NO.13467一樣,重發ACK包給server。
  • NO.44507 重復NO.16573的步驟
  • NO.79195 繼續重復NO.16573的步驟
  • NO.79195 server立刻直接回了RST包,結束會話

詳細的包內容備注在后面,需要關注的不光是包發送順序,包的seq重傳之類,還有一個重要的,TCP retransmission timeout,即TCP超時重傳。對於這里已經抓到的數據包,wireshark可以看下每次超時重傳的時間間隔,如下圖: 
tcp ack rto 重傳數據包 
RTO的重傳次數是系統可配置的,見/proc/sys/net/ipv4/tcp_retries1 ,而重傳時間間隔,間隔增長頻率等,是比較復雜的方式計算出來的,見《 TCP/IP重傳超時–RTO 》。

backlog大小設置為多少合適? 
從上面的結論中可以看出,這跟FPM的處理能力有關,backlog太大了,導致FPM處理不過來,nginx那邊等待超時,斷開連接,報504 gateway timeout錯。同時FPM處理完准備write 數據給nginx時,發現TCP連接斷開了,報“Broken pipe”。backlog太小的話,NGINX之類client,根本進入不了FPM的accept queue,報“502 Bad Gateway”錯。所以,這還得去根據FPM的QPS來決定backlog的大小。計算方式最好為QPS=backlog。對了這里的QPS是正常業務下的QPS,千萬別用echo hello world這種結果的QPS去欺騙自己。當然,backlog的數值,如果指定在FPM中的話,記得把操作系統的net.core.somaxconn設置的起碼比它大。另外,ubuntu server 1404上/proc/sys/net/core/somaxconn 跟/proc/sys/net/ipv4/tcp_max_syn_backlog 默認值都是128,這個問題,我為了抓數據,測了好幾遍才發現。 
對於測試時,TCP數據包已經drop掉的未進入syns queue,以及未進入accept queue的數據包,可以用netstat -s來查看:

 root@vmware-cnxct:/# netstat -s TcpExt: //... 5 times the listen queue of a socket overflowed 24 SYNs to LISTEN sockets dropped //未進入syns queue的數據包數量 packets directly queued to recvmsg prequeue. 8 bytes directly in process context from backlog //... TCPSackShiftFallback: 27 TCPBacklogDrop: 2334 //未進入accept queue的數據包數量 TCPTimeWaitOverflow: 229347 TCPReqQFullDoCookies: 11591 TCPRcvCoalesce: 29062 //...

經過相關資料查閱,技術點研究,再做一番測試之后,又加深了我對TCP通訊知識點的記憶,以及對sync queue、accept queue所處環節知識點薄弱的補充,也是蠻有收獲,這些知識,在以后的純TCP通訊程序研發過程中,包括高性能的互聯網通訊中,想必有很大幫助,希望自己能繼續找些案例來實踐檢驗一下對這些知識的掌握。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM