小強(qiáng)性能測(cè)試2016年11月6號(hào)開(kāi)課。
小強(qiáng)python(接口+selenium+Appium)全棧自動(dòng)化測(cè)試班2017年2月開(kāi)課
報(bào)名QQ:2083503238、1684129674、480934277(所有學(xué)員享受終生免費(fèi)不限次數(shù)重學(xué)的福利!)
介紹見(jiàn)http://xqtesting.sxl.cn
在使用Nginx時(shí),經(jīng)常會(huì)碰到502 Bad Gateway和504 Gateway Time-out錯(cuò)誤,下面以Nginx+PHP-FPM來(lái)分析下這兩種常見(jiàn)錯(cuò)誤的原因和解決方案。
1.502 Bad Gateway錯(cuò)誤
在php.ini和php-fpm.conf中分別有這樣兩個(gè)配置項(xiàng):max_execution_time和request_terminate_timeout。
這兩項(xiàng)都是用來(lái)配置一個(gè)PHP腳本的最大執(zhí)行時(shí)間的。當(dāng)超過(guò)這個(gè)時(shí)間時(shí),PHP-FPM不只會(huì)終止腳本的執(zhí)行,還會(huì)終止執(zhí)行腳本的Worker進(jìn)程。所以Nginx會(huì)發(fā)現(xiàn)與自己通信的連接斷掉了,就會(huì)返回給客戶(hù)端502錯(cuò)誤。
以PHP-FPM的request_terminate_timeout=30秒時(shí)為例,報(bào)502 Bad Gateway錯(cuò)誤的具體信息如下:
1)Nginx錯(cuò)誤訪問(wèn)日志:
2013/09/19 01:09:00 [error] 27600#0: *78887 recv() failed (104: Connection reset by peer) while reading response header from upstream,
client: 192.168.1.101, server: test.com, request: "POST /index.php HTTP/1.1", upstream: "fastcgi://unix:/dev/shm/php-fcgi.sock:",
host: "test.com", referrer: "http://test.com/index.php"
2)PHP-FPM報(bào)錯(cuò)日志:
WARNING: ?child 25708 exited on signal 15 (SIGTERM) after 21008.883410 seconds from start
所以只需將這兩項(xiàng)的值調(diào)大一些就可以讓PHP腳本不會(huì)因?yàn)閳?zhí)行時(shí)間長(zhǎng)而被終止了。request_terminate_timeout可以覆蓋max_execution_time,所以如果不想改全局的php.ini,那只改PHP-FPM的配置就可以了。
解決辦法是request_terminate_timeout設(shè)置為10s或者一個(gè)合理的值,或者給file_get_contents加一個(gè)超時(shí)參數(shù)。
$ctx= stream_context_create(array(
'http'=>array(
'timeout'=> 10//設(shè)置一個(gè)超時(shí)時(shí)間,單位為秒
)
)
);
file_get_contents($str, 0,$ctx);
此外要注意的是Nginx的upstream模塊中的max_fail和fail_timeout兩項(xiàng)。有時(shí)Nginx與上游服務(wù)器(如Tomcat、FastCGI)的通信只是偶然斷掉了,但max_fail如果設(shè)置的比較小的話(huà),那么在接下來(lái)的fail_timeout時(shí)間內(nèi),Nginx都會(huì)認(rèn)為上游服務(wù)器掛掉了,都會(huì)返回502錯(cuò)誤。所以可以將max_fail調(diào)大一些,將fail_timeout調(diào)小一些。
2、max_requests參數(shù)配置不當(dāng),可能會(huì)引起間歇性502錯(cuò)誤:
pm.max_requests = 1000
#設(shè)置每個(gè)子進(jìn)程重生之前服務(wù)的請(qǐng)求數(shù). 對(duì)于可能存在內(nèi)存泄漏的第三方模塊來(lái)說(shuō)是非常有用的. 如果設(shè)置為 '0' 則一直接受請(qǐng)求. 等同于 PHP_FCGI_MAX_REQUESTS 環(huán)境變量. 默認(rèn)值: 0.
這段配置的意思是,當(dāng)一個(gè) PHP-CGI 進(jìn)程處理的請(qǐng)求數(shù)累積到 500 個(gè)后,自動(dòng)重啟該進(jìn)程。
但是為什么要重啟進(jìn)程呢?
一般在項(xiàng)目中,我們多多少少都會(huì)用到一些 PHP 的第三方庫(kù),這些第三方庫(kù)經(jīng)常存在內(nèi)存泄漏問(wèn)題,如果不定期重啟 PHP-CGI 進(jìn)程,勢(shì)必造成內(nèi)存使用量不斷增長(zhǎng)。因此 PHP-FPM 作為 PHP-CGI 的管理器,提供了這么一項(xiàng)監(jiān)控功能,對(duì)請(qǐng)求達(dá)到指定次數(shù)的 PHP-CGI 進(jìn)程進(jìn)行重啟,保證內(nèi)存使用量不增長(zhǎng)。
正是因?yàn)檫@個(gè)機(jī)制,在高并發(fā)的站點(diǎn)中,經(jīng)常導(dǎo)致 502 錯(cuò)誤,我猜測(cè)原因是 PHP-FPM 對(duì)從 NGINX 過(guò)來(lái)的請(qǐng)求隊(duì)列沒(méi)處理好。不過(guò)我目前用的還是 PHP 5.3.2,不知道在 PHP 5.3.3 中是否還存在這個(gè)問(wèn)題。
目前我們的解決方法是,把這個(gè)值盡量設(shè)置大些,盡可能減少 PHP-CGI 重新 SPAWN 的次數(shù),同時(shí)也能提高總體性能。在我們自己實(shí)際的生產(chǎn)環(huán)境中發(fā)現(xiàn),內(nèi)存泄漏并不明顯,因此我們將這個(gè)值設(shè)置得非常大(204800)。大家要根據(jù)自己的實(shí)際情況設(shè)置這個(gè)值,不能盲目地加大。
話(huà)說(shuō)回來(lái),這套機(jī)制目的只為保證 PHP-CGI 不過(guò)分地占用內(nèi)存,為何不通過(guò)檢測(cè)內(nèi)存的方式來(lái)處理呢?我非常認(rèn)同高春輝所說(shuō)的,通過(guò)設(shè)置進(jìn)程的峰值內(nèi)在占用量來(lái)重啟 PHP-CGI 進(jìn)程,會(huì)是更好的一個(gè)解決方案。
3.504 Gateway Time-out錯(cuò)誤
PHP-FPM設(shè)置的腳本最大執(zhí)行時(shí)間已經(jīng)夠長(zhǎng)了,但執(zhí)行耗時(shí)PHP腳本時(shí),發(fā)現(xiàn)Nginx報(bào)錯(cuò)從502變?yōu)?04了。這是為什么呢?
因?yàn)槲覀冃薷牡闹皇荘HP的配置,Nginx中也有關(guān)于與上游服務(wù)器通信超時(shí)時(shí)間的配置factcgi_connect/read/send_timeout。
以Nginx超時(shí)時(shí)間為90秒,PHP-FPM超時(shí)時(shí)間為300秒為例,報(bào)504 Gateway Timeout錯(cuò)誤時(shí)的Nginx錯(cuò)誤訪問(wèn)日志如下:
2013/09/19 00:55:51 [error] 27600#0: *78877 upstream timed out (110: Connection timed out) while reading response header from upstream,
client: 192.168.1.101, server: test.com, request: "POST /index.php HTTP/1.1", upstream: "fastcgi://unix:/dev/shm/php-fcgi.sock:",
host: "test.com", referrer: "http://test.com/index.php"
調(diào)高這三項(xiàng)的值(主要是read和send兩項(xiàng),默認(rèn)不配置的話(huà)Nginx會(huì)將超時(shí)時(shí)間設(shè)為60秒)之后,504錯(cuò)誤也解決了。
而且這三項(xiàng)配置可以配置在http、server級(jí)別,也可以配置在location級(jí)別。擔(dān)心影響其他應(yīng)用的話(huà),就配置在自己應(yīng)用的location中吧。要注意的是factcgi_connect/read/send_timeout是對(duì)FastCGI生效的,而proxy_connect/read/send_timeout是對(duì)proxy_pass生效的。
配置舉例:
location ~ \.php$ {
root ? ? ? ? ? ? ? ? ? /home/cdai/test.com;
include ? ? ? ? ? ? ? ? fastcgi_params;
fastcgi_connect_timeout ? ? ?180;
fastcgi_read_timeout ? ? ? ?600;
fastcgi_send_timeout ? ? ? ?600;
fastcgi_pass ? ? ? ? ? ? ?unix:/dev/shm/php-fcgi.sock;
fastcgi_index ? ? ? ? ? ? index.php;
fastcgi_param ? ? SCRIPT_FILENAME/home/cdai/test.com$fastcgi_script_name;
}