在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
如圖,服務端收到客戶端的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包數量吻合。 apache
在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包。
對於已經調用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的定時器系列 — 超時重傳定時器 )
對於這些結論,我嘗試搜了不少資料,後來在360公司的「基礎架構快報」中也看到了他們的研究資料《 TCP三次握手之backlog 》,也驗證了個人結論。架構
關於ACCEPT QUEUE滿了以後的表現問題,早上 IM鑫爺 給我指出幾個錯誤,感謝批評及指導,在這裏,我把這個問題再詳細描述一下。如上圖所示ssh
詳細的包內容備註在後面,須要關注的不光是包發送順序,包的seq重傳之類,還有一個重要的,TCP retransmission timeout,即TCP超時重傳。對於這裏已經抓到的數據包,wireshark能夠看下每次超時重傳的時間間隔,以下圖:
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通信程序研發過程當中,包括高性能的互聯網通信中,想必有很大幫助,但願本身能繼續找些案例來實踐檢驗一下對這些知識的掌握。