結(jié)合去年國(guó)慶和過(guò)年期間平臺(tái)碰到的一些問(wèn)題邀层,下面主要介紹的是PHP里面會(huì)涉及到的各種超時(shí)以及其中存在的坑脱柱。
Nginx的超時(shí)配置
fastcgi_connct_timeout 60
Nginx和fastcgi進(jìn)程建立連接的超時(shí)時(shí)間栽惶,默認(rèn)60秒署惯,如果超過(guò)了這個(gè)時(shí)間就斷開連接漫蛔。
fastcgi_read_timeout 300
和fastcgi進(jìn)程建立連接后獲得fastcgi進(jìn)程響應(yīng)的超時(shí)時(shí)間抛蚤,默認(rèn)60秒,如果超過(guò)了這個(gè)時(shí)間都沒(méi)有獲得響應(yīng)就斷開連接姓言。我們經(jīng)常碰到的是'504 Gateway Time-out'瞬项,就是因?yàn)楹蠖诉B接沒(méi)有在超時(shí)時(shí)間內(nèi)返回?cái)?shù)據(jù)導(dǎo)致的。我們經(jīng)常碰到的是'502 Bad Gateway'何荚,是因?yàn)閒astcig進(jìn)程報(bào)錯(cuò)囱淋,導(dǎo)致連接斷開。
fastcgi_send_timeout 300
Nginx向fastcgi進(jìn)程發(fā)送請(qǐng)求的超時(shí)時(shí)間餐塘,默認(rèn)60秒妥衣,如果超過(guò)了這個(gè)時(shí)間都沒(méi)有發(fā)送完就斷開連接〗渖担可以通過(guò)上傳比較大的文件税手,就會(huì)出現(xiàn)超時(shí),然后就會(huì)返回'504 Gateway Time-out'需纳。
PHP芦倒,PHP-FPM 的超時(shí)配置
max_execution_time 300
這個(gè)參數(shù)是在php.ini中設(shè)置的,說(shuō)實(shí)在的這個(gè)參數(shù)沒(méi)有什么太大的意義不翩,因?yàn)檫@個(gè)300秒的超時(shí)時(shí)間僅僅是統(tǒng)計(jì)本身代碼的執(zhí)行時(shí)間兵扬,不包括網(wǎng)絡(luò)請(qǐng)求,系統(tǒng)調(diào)用口蝠,數(shù)據(jù)庫(kù)查詢器钟,sleep()等的時(shí)間,如果超過(guò)這個(gè)時(shí)間會(huì)產(chǎn)生一個(gè)'Fatal error: Maximum execution time'的錯(cuò)誤妙蔗,然后返回的是'500 Internal Server Error'傲霸。我們程序大部分的時(shí)間都是花在網(wǎng)絡(luò)請(qǐng)求,數(shù)據(jù)庫(kù)查詢方面的。
request_terminate_timeout 0
這個(gè)參數(shù)是在php-fpm中設(shè)置的昙啄,這個(gè)超時(shí)時(shí)間就是整個(gè)fastcgi花費(fèi)的所有時(shí)間乃摹,這個(gè)和
max_execution_time
最大的不同,如果總時(shí)間超過(guò)了,會(huì)直接將FPM進(jìn)程kill掉跟衅,然后返回'502 Bad Gateway'。很多人認(rèn)為配置了這個(gè)參數(shù)max_execution_time
就失效了播歼,實(shí)際不是的伶跷,先達(dá)到哪個(gè)的超時(shí)時(shí)間就哪個(gè)配置起作用的。
建議是不要開啟這個(gè)參數(shù)秘狞,因?yàn)槿绻隳硞€(gè)程序超時(shí)了叭莫,進(jìn)程直接kill掉,你的數(shù)據(jù)完整性就沒(méi)有辦法保證了烁试,可以在nginx那邊做連接超時(shí)的控制和做好程序請(qǐng)求第三方資源超時(shí)時(shí)間的控制雇初。
接口請(qǐng)求方面的超時(shí)設(shè)置
這部分要特別注意,在沒(méi)有什么并發(fā)量的時(shí)候沒(méi)有什么問(wèn)題减响,在并發(fā)量大的時(shí)候靖诗,如果有些對(duì)接的第三方系統(tǒng)掛了或是處理速度很慢了,你的FPM進(jìn)程很快就會(huì)用完支示,然后就是各種502刊橘,然后很有可能的就是系統(tǒng)崩潰了。我們?cè)诳蚣軐用嫠毯瑁瑢?duì)需要用到的請(qǐng)求方法做了統(tǒng)一的封裝促绵。
/**
* CURL 提交請(qǐng)求數(shù)據(jù),現(xiàn)在基本的接口都是使用curl進(jìn)行post請(qǐng)求
*
* @author dwer
* @date 2016-04-11
* @param string $url 請(qǐng)求URL
* @param string $postData 請(qǐng)求發(fā)送的數(shù)據(jù)
* @param int $port 請(qǐng)求端口
* @param int $timeout 超時(shí)時(shí)間
* @param array $headers 請(qǐng)求頭信息
* @return bool|mixed
*/
function pft_curl_post($url, $postData, $port = 80, $timeout = 25, $headers = []) {
//超時(shí)時(shí)間處理
$timeout = intval($timeout);
$timeout = $timeout <= 0 ? 25 : $timeout;
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_PORT, $port);
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, 0);
if ((is_array($headers) || is_object($headers)) && count($headers)) {
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
}
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
$res = curl_exec($ch);
//錯(cuò)誤處理
$errCode = curl_errno($ch);
if ($errCode > 0) {
curl_close($ch);
return false;
} else {
//獲取HTTP碼
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($httpCode != 200) {
curl_close($ch);
return false;
} else {
curl_close($ch);
return $res;
}
}
}
file_get_contents的封裝
/**
* 統(tǒng)一封裝的file_get_contents, 原生的file_get_contents絕對(duì)不能使用嘴纺,沒(méi)有設(shè)置超時(shí)時(shí)間败晴,如果碰到網(wǎng)絡(luò)問(wèn)題,這個(gè)進(jìn)程會(huì)一直卡在那邊栽渴,無(wú)情地占用你的網(wǎng)絡(luò)連接尖坤。
* @author dwer
*
* @param string $url 請(qǐng)求url
* @param integer $timeout 超時(shí)時(shí)間
* @param array $header 請(qǐng)求頭部
* @return
*/
function pft_file_get_contents($url, $timeout = 10, $header = []){
$url = strval($url);
$timeout = intval($timeout);
$timeout = $timeout <= 0 ? 10 : $timeout;
$contextOptions = [
'http' => ['timeout' => $timeout]
];
if($header) {
$contextOptions['http']['header'] = $header;
}
$context = stream_context_create($contextOptions);
$res = file_get_contents($url, false, $context);
return $res;
}
SoapClient的封裝
/**
* 統(tǒng)一封裝的SOAP客戶端封裝,有些系統(tǒng)還在使用soap協(xié)議提供接口
* @author dwer
*
* 用法是一樣的闲擦,只是添加了設(shè)置超時(shí)的時(shí)間的方法
* $soapClient = new PftSoapClient('xxx.wsdl');
* $soapClient->setTimeout(25);
* $soapClient->getMyMoney($params);
*/
class PftSoapClient extends \SoapClient {
//超時(shí)的時(shí)間
private $timeout = 0;
//設(shè)置超時(shí)時(shí)間
public function setTimeout($timeout) {
$timeout = intval($timeout);
$timeout = $timeout <= 0 ? 25 : $timeout;
$this->timeout = $timeout;
}
//請(qǐng)求接口
public function __doRequest($request, $location, $action, $version, $oneWay = FALSE) {
if ($this->timeout <= 0) {
//使用默認(rèn)的方式
$res = parent::__doRequest($request, $location, $action, $version, $oneWay);
} else {
//使用添加了超時(shí)的方式
$socketTime = ini_get('default_socket_timeout');
ini_set('default_socket_timeout', $this->timeout);
$res = parent::__doRequest($request, $location, $action, $version, $oneWay);
ini_set('default_socket_timeout', $socketTime);
}
return $res;
}
}
數(shù)據(jù)庫(kù),Redis方面的超時(shí)
在Mysql和Redis服務(wù)器的配置中就會(huì)設(shè)置wait_timeout 和 timeout 參數(shù)糖驴,保證連接在超時(shí)沒(méi)有連接的情況下斷開連接。在程序方面需要做的就是處理超時(shí)后的'The MySQL server has gone away' 和 'PHP Fatal error: Uncaught exception 'RedisException' with message 'read error on connection'' 的異常捕獲和重連接處理佛致。不過(guò)正常情況下贮缕,這些在底層框架應(yīng)該都做了統(tǒng)一的封裝。