現(xiàn)在線上系統(tǒng)的架構大致是這樣的,除去cache的proxy機器外,還有項目的nginx proxy機器,后面跟nginx webserver + php-fpm。有時候,會看到proxy nginx的日志里面會有各種異常狀態(tài)碼,比如499,502,504等,這些是什么情況導致的呢?最近一一測試了下。
架構示意
nginx proxy => nginx webserver => php-fpm
狀態(tài)碼說明
499:客戶端(或者proxy)主動斷開連接
502:網(wǎng)關錯誤(Bad Gateway)
504:網(wǎng)關超時:(Gateway Timeout)
1 proxy和webserver不能連接
1.1 proxy_pass ip不存在
這時候會重復發(fā)送arp解析協(xié)議,約3秒后超時,proxy返回碼為502。
1.2 proxy_pass ip存在
1)webserver機器上端口上沒有對應服務。
webserver所在機器的內核會直接返回RESET包,沒有額外超時,proxy返回碼為502。
2)webserver機器端口上有服務,但是iptables DROP了proxy的包
因為webserver drop(iptables -I INPUT -s xxx.xxx.xxx.xxx -j DROP
)了proxy的包,proxy會TCP連接不斷重試,默認會重試60秒后proxy返回碼504,這個重試時間60秒由參數(shù) proxy_connect_timeout
指定,重試間隔就是TCP的重試間隔(1,2,4...)。
如果在超時之前,客戶端主動關閉連接(比如停止瀏覽器的請求),這個時候proxy會記錄 499狀態(tài)碼,而且$request_time
記錄的是proxy已經處理的時間,而$upstream_response_time
為 -
??蛻舳酥鲃雨P閉后,proxy也不會再向webserver發(fā)送重試請求。
但是如果你在proxy配置了proxy_ignore_client_abort on;
,那么即便客戶端主動關閉,proxy還是會不停的發(fā)送重試請求到webserver,直至超時,記錄的狀態(tài)碼為webserver返回的狀態(tài)碼。
3) webserver機器端口有服務,但是iptables REJECT了proxy的包
因為webserver reject(iptables -I INPUT -s xxx.xxx.xxx.xxx -j REJECT
)了proxy的包,與drop不同之處在于,這個時候webserver會返回一個端口不可達的ICMP包給proxy,proxy會重試一次后返回 502 給客戶端,超時時間約為1秒。
2 proxy和webserver連接正常(請求時間過長)
proxy的nginx.conf中的proxy_read_timeout=60
webserver的nginx.conf中fastcgi_read_timeout=300
php-fpm中的 request_terminate_timeout=120
2.1 php執(zhí)行時間超過proxy的proxy_read_timeout
假設php-fpm有一個test.php執(zhí)行時間為100秒,超過了默認的proxy_read_timeout=60;
,則到1分鐘后proxy會關閉到webserver的連接,webserver記錄的返回碼為499,proxy的返回碼為 504,客戶端看到的返回碼也就是 504。
關于proxy_read_timeout
要多說一句,在nginx文檔中可以看到這個參數(shù)的含義是
The timeout is set only between two successive read operations,
not for the transmission of the whole response.
意思是這個超時不是整個response的傳輸超時,而是兩次讀操作之間的間隔超時。比如在proxy中設置proxy_read_timeout=10
,而測試的 test.php 如下:
<?php
sleep(7);
echo "haha\n";
ob_flush();
flush();
sleep(7);
echo "haha after 7s\n";
?>
這整個請求的響應時間是14秒,其實是不會超時的,因為相鄰兩次讀操作的間隔是7秒小于10秒。注意代碼中的ob_flush()
和flush()
兩個函數(shù),其中ob_flush()
是為了刷php的緩存,而flush()
則是為了刷系統(tǒng)層面的緩存。當然如果你將 /etc/php5/fpm/php.ini
中設置output_buffering=off
,則可以不用調用ob_flush()
了,但是flush()
還是需要的。如果不flush的話,php會等到整個響應完成才會將數(shù)據(jù)返回給webserver,webserver再返回給proxy,在沒有返回整個響應之前(14秒才能返回),超過了 proxy_read_timeout的10秒,此時,proxy會關閉和webserver的連接,導致出現(xiàn)504錯誤。 為了這個測試test.php不超時,webserver的nginx還要加一個配置 fastcgi_buffering off;
,因為雖然我們的php返回了數(shù)據(jù)了,但是webserver的nginx還是緩存了fastcgi的返回,導致沒有及時將數(shù)據(jù)返回給proxy,從而超時。
在如上面配置好后,可以發(fā)現(xiàn),瀏覽器輸出了haha\nhaha after 7s
。問題來了,這兩個字符串是同時輸出的,并沒有像代碼中那樣隔了7秒,那這個問題是什么導致的呢?想必你應該知道了,proxy的nginx也有緩存配置,需要關閉才能看到先后輸出兩個字符串的效果。nginx proxy的緩存配置為proxy_buffering off;
,這樣你就能看到先后輸出兩個字符串的效果了。
2.2 php執(zhí)行時間超過webserver的fastcgi_read_timeout
設置fastcgi_read_timeout=10
,test.php執(zhí)行時間100秒,則10秒后webserver會關閉和PHP的連接,webserver記錄日志的返回碼為 504,proxy日志的返回碼也是 504。
2.3 php執(zhí)行時間超過php-fpm的request_terminate_timeout
設置request_terminate_timeout=5
,test.php還是執(zhí)行100秒,可以發(fā)現(xiàn)5秒之后,php-fpm會終止對應的php子進程,webserver日志的狀態(tài)碼為 404,proxy的日志的狀態(tài)碼也是 404。
注:經測試,在php-fpm模式中,php.ini中的max_execution_time
參數(shù)沒有什么效果。
3 關于文件數(shù)問題
Linux里面的一些限制參數(shù)可以通過 ulimit -a
查看。比如我的debian8.2系統(tǒng)的輸出如下:
# ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 96537
max locked memory (kbytes, -l) 64
max memory size (kbytes, -m) unlimited
open files (-n) 1000000
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 96537
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
其中open files
是一個進程可以同時打開的文件數(shù),超過了這個數(shù)會報too many open files錯誤
,修改open files
可以通過 ulimit -n xxx
實現(xiàn)。而max user processes
則是用戶最多創(chuàng)建的進程數(shù)。
另外,系統(tǒng)允許打開的最大文件數(shù)在配置file-max
中。
# cat /proc/sys/fs/file-max
2471221
要修改file-max
,可以通過
# sysctl -w fs.file-max=1000000
用永久生效,需要在/etc/sysctl.conf
中加入這行
fs.file-max=1000000
然后sysctl -p
即可生效。
要針對用戶限制文件數(shù)之類的,可以修改/etc/security/limits.conf
,內容格式如下:
<domain> <type> <item> <value>
## 比如限制 bob這個用戶的一個進程同時打開的文件數(shù)
## Example hard limit for max opened files
bob hard nofile 4096
## Example soft limit for max opened files
bob soft nofile 1024
nginx配置中的worker_rlimit_nofile
可以配置為open files
這個值。