PHP 提示错误:

==> /usr/local/nginx/logs/error.log <==
2017/12/27 08:36:08 [error] 20443#0: *139 upstream timed out (60: Operation timed out) while reading response header from upstream, client: 127.0.0.1, server: 127.0.0.1, request: "GET /api/header.php?url=http://bjetc.cn&resheaders=1 HTTP/1.1", upstream: "fastcgi://127.0.0.1:9000", host: "localhost:8084"

==> /usr/local/nginx/logs/host.access.log <==
2017-12-27T08:36:08+08:00 127.0.0.1 - "GET /50x.html?-" 504 150.000 537 "-" "curl/7.54.0" "-"

 

 

原因分析

环境:Mac OS + Nginx + php-fpm

从提示信息看,是读取流文件,如读取文件、网络连接(Linux中也是流文件读取)超时,服务器返回504

504 状态码含义:关口过载,服务器使用另一个关口或服务来响应用户,等待时间设定值较长

502状态码含义:服务器暂时不可用,有时是为了防止发生系统过载

更多状态码请见米扑博客:HTTP返回状态值详解

一般的原因是读取的文件太大、不存在,或网络连接超时、网站无法访问等。

Nginx 504 Gateway Time-out的含义是所请求的网关没有请求到,简单来说就是没有请求到可以执行的PHP-CGI

Nginx 502 Bad Gateway的含义是请求的PHP-CGI已经执行,但是由于某种原因(一般是读取资源的问题)没有执行完毕而导致PHP-CGI进程终止

一般来说Nginx 502 Bad Gateway和php-fpm.conf的设置有关,而Nginx 504 Gateway Time-out则是与nginx.conf的设置有关

 

 

解决方法

1. 开启慢日志监控

1)在 php-fpm.conf 开启慢日志(默认是关闭的)

vim /usr/local/php/etc/php-fpm.conf

删除分号,取消注释,修改后如下:

slowlog = log/$pool.log.slow

;request_slowlog_timeout = 0
request_slowlog_timeout = 5

 

2)创建日志文件夹目录 log ,用于保存 $pool.log.slow

其中 pool 一般为 www,完整日志文件名为 /usr/local/php5/log/www.log.slow

mkdir /usr/local/php/log

说明:

1)slowlog 用于保存慢日志,一般配合 request_slowlog_timeout 一起使用

2)request_slowlog_timeout 表示请求处理慢于多少时间的为慢请求,request_slowlog_timeout = 0表示关闭慢日志

例如 request_slowlog_timeout = 5 表示请求处理时间慢于 5秒的为慢请求,写入慢日志文件

 

3)重启 php-fpm 使其生效

/etc/init.d/php-fpm restart

 

4)查看慢日志

tail -f /usr/local/php5/log/*.log.slow 

日志记录如下:

$ tail -f /usr/local/php5/log/*.log.slow    

[27-Dec-2017 10:03:07]  [pool www] pid 24098
script_filename = /Users/homer/workspace/mimvp_site/api/header.php
[0x000000010643d1d8]
flock() /Users/homer/workspace/mimvp_site/api/header.php:876
[0x000000010643cf50]
lock() /Users/homer/workspace/mimvp_site/api/header.php:91

[27-Dec-2017 10:22:00]  [pool www] pid 31362
script_filename = /Users/homer/workspace/mimvp_site/api/header.php
[0x0000000106ac6820]
curl_exec() /Users/homer/workspace/mimvp_site/api/header.php:684
[0x0000000106ac60d0] get_siteurl_curlinfo() /Users/homer/workspace/mimvp_site/api/header.php:258


[27-Dec-2017 10:22:55]  [pool www] pid 31359
script_filename = /Users/homer/workspace/mimvp_site/api/header.php
[0x0000000106ac7fc0]
curl_exec() /opt/php-selenium/vendor/facebook/webdriver/lib/remote/HttpCommandExecutor.php:225
[0x0000000106ac6e08] execute() /opt/php-selenium/vendor/facebook/webdriver/lib/remote/RemoteWebDriver.php:491
[0x0000000106ac6bb8] execute() /opt/php-selenium/vendor/facebook/webdriver/lib/remote/RemoteWebDriver.php:171
[0x0000000106ac6960] get() /Users/homer/workspace/mimvp_site/api/header.php:724
[0x0000000106ac5f50] get_siteurl_selenium() /Users/homer/workspace/mimvp_site/api/header.php:285

根据慢日志,查看代码 flock() /Users/homer/workspace/mimvp_site/api/header.php:876

return flock($this->fp, LOCK_EX); // 独占锁定

请求超时的原因在于文件锁,锁定后未及时释放,改进方案就是try - catch 捕获异常,释放锁

根据慢日志,查看代码 curl_exec() /Users/homer/workspace/mimvp_site/api/header.php:684

原因是 php curl 执行超时,selenium + webdriver 的 curl_exec 也是curl超时,需设置curl超时参数

$ch = curl_init();
curl_setopt($ch, CURLOPT_AUTOREFERER, 1);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);		// HTTP 头中的 "Location: "重定向
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);		// 字符串返回
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE); 	// https请求 不验证证书和hosts
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
curl_setopt($ch, CURLOPT_HEADER, 1);			// 0表示不输出Header,1表示输出
curl_setopt($ch, CURLOPT_NOBODY, 0);			// 0表示不输出Body,1表示输出
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);		// 尝试连接时等待的秒数。设置为0,则无限等待
curl_setopt($ch, CURLOPT_TIMEOUT, 10);			// 允许 cURL 函数执行的最长秒数
curl_setopt($ch, CURLOPT_URL, $url);
$output = curl_exec($ch);
curl_close($ch);

 

 

2. 设置超时和缓存大小

nginx 的超时和缓存大小,也会影响请求的响应速度

修改nginx配置文件 nginx.conf

vim /usr/local/nginx/conf/nginx.conf

添加配置如下:

http {
    include       mime.types;
    default_type  application/octet-stream;
    charset       utf-8;

    log_format   main   '$time_iso8601 $remote_addr - "$request_method $uri?$args" '
                        '$status $request_time $body_bytes_sent "$http_referer" '
                        '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  logs/access.log  main;

    sendfile        on;
    tcp_nopush     	on;
    tcp_nodelay         on;

    #keepalive_timeout  0;
    keepalive_timeout  65;
    server_tokens   off;


    fastcgi_connect_timeout     300;
    fastcgi_send_timeout        300;
    fastcgi_read_timeout        300;
    fastcgi_buffer_size         256k;
    fastcgi_buffers          8  256k;
    fastcgi_busy_buffers_size   512k;
    fastcgi_temp_file_write_size        256k;
    fastcgi_intercept_errors    on;


    proxy_connect_timeout       300;
    proxy_read_timeout          300;
    proxy_send_timeout          300;
    proxy_buffer_size           256k;
    proxy_buffers            8  256k;
    proxy_busy_buffers_size     512k;
    proxy_temp_file_write_size  256k;


    gzip  on;
    gzip_min_length             1k;
    gzip_buffers            4   256k;
    gzip_proxied                off;
    gzip_http_version           1.1;
    gzip_comp_level             4;
    gzip_types                  text/plain text/xml text/css text/javascript application/json application/x-javascript application/xml application/xml+css;
    gzip_vary                   on;

    client_header_buffer_size           128k;
    large_client_header_buffers    8    256k;
    client_body_buffer_size             16m;
    client_max_body_size                320m;
    client_header_timeout       5;
    client_body_timeout         10;

如上,重点看 fastcgi 和 proxy 配置,包含超时和缓存大小

 

 

3. 优化配置文件 php-fpm.conf

nginx 转发请求给 php-fpm 处理,再转给 php-cgi 处理,因此瓶颈问题一般出在 php-fpm

php-fpm.conf有两个至关重要的参数,一个是 max_children,另一个是 request_terminate_timeout

max_children

这个值又是怎么计算出来的呢?这个值原则上是越大越好,php-cgi的进程多了就会处理的很快,排队的请求就会很少。

设置 max_children 需要根据服务器的性能进行设定,一般来说一台服务器正常情况下每一个php-cgi所耗费的内存在20M左右,因此内存1GB 则 max_children 设置成40个,20M*40=800M也就是说在峰值的时候所有PHP-CGI所耗内存在800M以内,低于有效内存1GB。而如果”max_children”设置的较小,比如5-10个,那么php-cgi就会“很累”,处理速度也很慢,等待的时间也较长。如果长时间没有得到处理的请求就会出现504 Gateway Time-out这个错误,而正在处理的很累的那几个php-cgi如果遇到了问题就会出现502 Bad gateway这个错误。

request_terminate_timeout

如果你的服务器性能足够好,且宽带资源足够充足,PHP脚本没有系循环或BUG的话你可以直接将”request_terminate_timeout” 设置成0

0 的含义是让PHP-CGI一直执行下去而没有时间限制。而如果做不到这一点,也就是说你的PHP-CGI可能出现某个BUG,或者你的宽带不够充足或者其他的原因导致你的PHP-CGI能够假死那么就建议你给”request_terminate_timeout”赋一个值,这个值可以 根据你服务器的性能进行设定。一般来说性能越好你可以设置越高,20-30分钟都可以。由于我的服务器PHP脚本需要长时间运行,有的可能会超过10 分钟因此我设置了1200秒(20分钟),这样不会导致PHP-CGI死掉而出现502 Bad gateway这个错误。如果把request_terminate_timeout由之前的0s改为60s,这样php-cgi进程处理脚本的超时时间就是60秒,可以防止进程都被挂起,提高利用效率,推荐设置为 60s

vim /usr/local/php/etc/php-fpm.conf

修改配置如下:

; The timeout for serving a single request after which the worker process will
; be killed. This option should be used when the 'max_execution_time' ini option
; does not stop script execution for some reason. A value of '0' means 'off'.
; Available units: s(econds)(default), m(inutes), h(ours), or d(ays)
; Default Value: 0
;request_terminate_timeout = 0
request_terminate_timeout = 60s

;syslog.ident = php-fpm
;   static  - a fixed number (pm.max_children) of child processes;
;             pm.max_children      - the maximum number of children that can
;             pm.start_servers     - the number of children created on startup.
;             pm.min_spare_servers - the minimum number of children in 'idle'
;             pm.max_spare_servers - the maximum number of children in 'idle'
;             pm.max_children           - the maximum number of children that
;             pm.process_idle_timeout   - The number of seconds after which
pm = dynamic
; The number of child processes to be created when pm is set to 'static' and the
; maximum number of child processes when pm is set to 'dynamic' or 'ondemand'.
; served. Equivalent to the ApacheMaxClients directive with mpm_prefork.
; forget to tweak pm.* to fit your needs.
; Note: Used when pm is set to 'static', 'dynamic' or 'ondemand'

;pm.max_children = 5
pm.max_children = 20

;pm.start_servers = 2
pm.start_servers = 8

;pm.min_spare_servers = 1
pm.min_spare_servers = 6

;pm.max_spare_servers = 3
pm.max_spare_servers = 10

;pm.process_idle_timeout = 10s;
;pm.max_requests = 500

 

 

参考推荐

PHP错误:Allowed memory size of 268435456 bytes exhausted (tried to allocate 87 bytes)