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 之類 )中能夠修改。我稍微整理了流程圖,以下: php

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的值。linux

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日誌數據以下: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-271apache

  在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的錯。ubuntu

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鑫爺 給我指出幾個錯誤,感謝批評及指導,在這裏,我把這個問題再詳細描述一下。如上圖所示ssh

  • 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 》。socket

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通信程序研發過程當中,包括高性能的互聯網通信中,想必有很大幫助,但願本身能繼續找些案例來實踐檢驗一下對這些知識的掌握。

相關文章
相關標籤/搜索