现在线上系统的架构大致是这样的,除去cache的proxy机器外,还有项目的nginx proxy机器,后面跟nginx webserver + php-fpm。有时候,会看到proxy nginx的日志里面会有各种异常状态码,比如499,502,504等,这些是什么情况导致的呢?最近一一测试了下。
架构示意
nginx proxy => nginx webserver => php-fpm
状态码说明
499:客户端(或者proxy)主动断开连接
502:网关错误(Bad Gateway)
504:网关超时:(Gateway Timeout)
1 proxy和webserver不能连接
1.1 proxy_pass ip不存在
这时候会重复发送arp解析协议,约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秒由参数 proxy_connect_timeout
指定,重试间隔就是TCP的重试间隔(1,2,4...)。
如果在超时之前,客户端主动关闭连接(比如停止浏览器的请求),这个时候proxy会记录 499状态码,而且$request_time
记录的是proxy已经处理的时间,而$upstream_response_time
为 -
。客户端主动关闭后,proxy也不会再向webserver发送重试请求。
但是如果你在proxy配置了proxy_ignore_client_abort on;
,那么即便客户端主动关闭,proxy还是会不停的发送重试请求到webserver,直至超时,记录的状态码为webserver返回的状态码。
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执行时间超过proxy的proxy_read_timeout
假设php-fpm有一个test.php执行时间为100秒,超过了默认的proxy_read_timeout=60;
,则到1分钟后proxy会关闭到webserver的连接,webserver记录的返回码为499,proxy的返回码为 504,客户端看到的返回码也就是 504。
关于proxy_read_timeout
要多说一句,在nginx文档中可以看到这个参数的含义是
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()
两个函数,其中ob_flush()
是为了刷php的缓存,而flush()
则是为了刷系统层面的缓存。当然如果你将 /etc/php5/fpm/php.ini
中设置output_buffering=off
,则可以不用调用ob_flush()
了,但是flush()
还是需要的。如果不flush的话,php会等到整个响应完成才会将数据返回给webserver,webserver再返回给proxy,在没有返回整个响应之前(14秒才能返回),超过了 proxy_read_timeout的10秒,此时,proxy会关闭和webserver的连接,导致出现504错误。 为了这个测试test.php不超时,webserver的nginx还要加一个配置 fastcgi_buffering off;
,因为虽然我们的php返回了数据了,但是webserver的nginx还是缓存了fastcgi的返回,导致没有及时将数据返回给proxy,从而超时。
在如上面配置好后,可以发现,浏览器输出了haha\nhaha after 7s
。问题来了,这两个字符串是同时输出的,并没有像代码中那样隔了7秒,那这个问题是什么导致的呢?想必你应该知道了,proxy的nginx也有缓存配置,需要关闭才能看到先后输出两个字符串的效果。nginx proxy的缓存配置为proxy_buffering off;
,这样你就能看到先后输出两个字符串的效果了。
2.2 php执行时间超过webserver的fastcgi_read_timeout
设置fastcgi_read_timeout=10
,test.php执行时间100秒,则10秒后webserver会关闭和PHP的连接,webserver记录日志的返回码为 504,proxy日志的返回码也是 504。
2.3 php执行时间超过php-fpm的request_terminate_timeout
设置request_terminate_timeout=5
,test.php还是执行100秒,可以发现5秒之后,php-fpm会终止对应的php子进程,webserver日志的状态码为 404,proxy的日志的状态码也是 404。
注:经测试,在php-fpm模式中,php.ini中的max_execution_time
参数没有什么效果。
3 关于文件数问题
Linux里面的一些限制参数可以通过 ulimit -a
查看。比如我的debian8.2系统的输出如下:
# 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
是一个进程可以同时打开的文件数,超过了这个数会报too many open files错误
,修改open files
可以通过 ulimit -n xxx
实现。而max user processes
则是用户最多创建的进程数。
另外,系统允许打开的最大文件数在配置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
即可生效。
要针对用户限制文件数之类的,可以修改/etc/security/limits.conf
,内容格式如下:
<domain> <type> <item> <value>
## 比如限制 bob这个用户的一个进程同时打开的文件数
## 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
这个值。