在nginx上跑php容易出现502错误,很多人遇到过这个问题,到底是什么原因导致的呢? 

出现502错误后,又有哪些解决办法呢? 

本文源起于前段时间处理无线浏览器的一次502报警后总结得来,希望能为大家提供参考。

从报警说起

现象 

1、打开静态页面很快,如无线浏览器的更新日志页面,请求云控接口却很慢,报502错误。 

2、最开始一周报警1-2次,后来发展到一天报警1次。

处理过程 

1、查看错误日志,发现502错误的响应时间都比较长,均为21s左右。见下图:

blob.png

日志中请求的是云控接口,就是一个读取配置文件然后吐出数据的过程,正常情况下响应时间应该是非常短的。 

因此直观猜测是php进程堵死了,云控请求过来时php进程不够用了,从而导致502错误。

使用 netstat -napo | grep "php-fpm" | wc -l 查看了一下当前fastcgi进程个数, 发现已达到php-fpm.conf中pm.max_children的设置值。 

重启php-fpm后,报警得以解除,但这并没有找到问题根源,php进程为什么会堵死呢?

2、猜测进程堵死的原因应该是大量的慢请求导致的。在/usr/local/php/etc/php-fpm.conf文件中,发现有关于慢日志的设置:

slowlog = /data/php/log/$pool.log.slow
request_slowlog_timeout = 10
request_terminate_timeout = 0

request_terminate_timeout配置的是一个php脚本的最大执行时间,默认是0s。 

0s的含义是让php脚本一直执行下去而没有时间限制。 

当再次收到报警时,查看php的slow log,果然发现发现大量执行时间超过10s的脚本。慢日志如下:

11-Aug-2014 08:34:53 pool www pid 8683
script_filename = /home/q/system/api.mse.360.cn/src/www/api//index.php0x00000000013d0420 curl_exec() /home/q/system/api.mse.360.cn/src/application/models/bizservice/icon_svc.php:291
0x00000000013cff98 httpRequest() /home/q/system/api.mse.360.cn/src/application/models/bizservice/icon_svc.php:203
0x00000000013cf710 getTitleBySite() /home/q/system/api.mse.360.cn/src/application/models/bizservice/icon_svc.php:33
0x00000000013cf260 getIconAndTitleBySite() /home/q/system/api.mse.360.cn/src/application/controllers/api/IconController.php:25
0x00000000013cf0c0 addSiteAction() /home/q/php/QFrameSmarty/web/QFrameaction.php:46
0x00000000013ce9a0 dispatch() /home/q/php/QFrameSmarty/web/QFrameweb.php:176
0x00000000013ce4b0 dispatch() /home/q/php/QFrameSmarty/web/QFrameweb.php:150
0x00000000013ce190 runController() /home/q/php/QFrameSmarty/web/QFrameweb.php:129
0x00000000013ce018 processRequest() /home/q/php/QFrameSmarty/web/QFrameweb.php:37
0x00000000013cdc50 run() /home/q/system/api.mse.360.cn/src/www/api/index.php:15

终于找到php进程堵死的根源:调用添加icon到浏览器主屏这个接口时,大量的curl抓取页面title执行超时。 

慢请求过多导致php进程不能及时释放,所以触发报警。 

添加curl的执行超时,上线代码后,php进程终于治“堵”成功,nginx+php-fpm的502报警彻底解除。

为什么会出现502

一般而言,nginx出现502有很多原因,但大都可以归结为资源数量不够用,也就是说后端php-fpm处理有问题。 

nginx将正确的客户端请求发给后端的php-fpm进程,由于php-fpm进程的问题导致不能正确解析php代码,最终返回502错误。

1、php-fpm进程数不够用 

nginx和fastcgi是通过socket进行连接的,一个php-fpm进程在同一时刻只能响应一个用户请求。它能处理的最大请求数由php-fpm.conf中如下配置决定:

listen.backlog = 128
pm.max_children = 256

这是我们线上服务器的配置,也就是说php-fpm能处理的最大请求数为384。 

当客户端的请求数超过这个值时,再试图发送请求会收到Resource temporarily unavailable,这时nginx就会报502错误。

上面提到的业务报警就是由于php-fpm进程数不够用而导致的。

大量慢请求占据了php进程,php-fpm进程数很快达到峰值。php-fpm被卡死不能处理新的请求时,nginx并不会收到请求被拒绝的类似信息,而是把请求积压在socket上;

此时浏览器所得到的响应就是一个“正在等待响应”的提示,除非socket报错或浏览器关闭否则等待永远不会停止。 

当积压数超过backlog的设置值时,高峰时间大量的云控请求就会触发服务端的502报警。

为了验证这一结论,在测试服务器上修改php-fpm.conf文件中的配置进行一个简单测试。 

修改php-fpm.conf的相关配置如下:

listen.backlog = 8
pm = dynamic pm.max_children = 8
pm.min_spare_servers = 2
pm.max_spare_servers = 4

重启php-fpm,客户端发送多于16个请求,每个请求脚本都sleep 10s。采用http_load进行压测,结果如下:

21 fetches, 25 max parallel, 18044 bytes, in 30.0058 seconds
859.238 mean bytes/connection
0.699864 fetches/sec, 601.35 bytes/sec
msecs/connect: 0.472571 mean, 0.637 max, 0.229 min
msecs/first-response: 17094.7 mean, 22743.7 max, 10002.3 min
5 bad byte counts
HTTP response codes:
code 200 – 16
code 502 – 5

从压测结果可以看到,php-fpm能处理的最大请求数应为16,超出这个值的请求会报502错误。

2、php脚本执行超时 

在php.ini和php-fpm.conf中分别有这样两个配置项:max_execution_time和request_terminate_timeout。 

文档中对其注释如下:;

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

这两项都是用来配置一个php脚本的最大执行时间的,默认为0s。 

当超过这个时间时,php-fpm不仅会终止脚本的执行,还会终止执行脚本的worker进程。 

当Nginx会发现与自己通信的socket连接断掉了,以为上游服务器挂了,于是返回给客户端502错误。

修改php-fpm.conf配置如下:

request_terminate_timeout = 10

重启php-fpm,写一个php脚本,sleep 20s。打开浏览器访问测试页面,执行该php脚本验证一下,结果如下图:

blob.png

由于php脚本执行时间超过配置规定时间,进程被异常终止,所以nginx返回502,验证了上面的结论。

如何避免502

如果服务器并发量非常大,那只能先增加机器,然后按以下方式优化会取得更好效果;但如果并发不大却出现502,一般都可以归结为上述两个原因,php-fpm进程数不够用和php脚本执行超时。

1、增加php-fpm进程数 

使用 netstat -napo | grep "php-fpm" | wc -l 查看一下当前fastcgi进程个数。 

如果个数接近 php-fpm.conf里配置的pm.max_children上限,就需要调高进程数。

因为max_children设置的较小,那么fastcgi就会“很累”,处理速度也很慢,等待的时间也较长。 

但设置max_children也会受到机器资源的限制,因此也并不能无限增加,增大进程数,内存占用也会相应增大。 

正常情况下每个php-fpm进程所耗费的内存在20M左右,因此我们线上服务器的配置基本上是最佳的。

2、调整request_terminate_timeout 

request_terminate_timeout可以根据服务器的性能进行设定,一般来说性能越好你可以设置越高。 

在我们的线上服务器建议直接使用默认值0s就可以了,这样能够避免php进程被异常终止而导致502错误。

可以看到,增加php-fpm进程数和调整request_terminate_timeout均属于配置问题。 

在我们的线上环境,相关配置基本上已达到最优,此时要避免502最最重要的还是要控制好程序中的超时。 

curl、file_get_contents等函数都要设置超时时间,mysql慢查询请求也要尽量避免。

总结一下

1、nginx出现502错误一般都是后端的php-fpm出问题引起的。 

2、pm.max_children不能无限增加,要视机器资源而定。 

3、php-fpm.conf中request_terminate_timeout最好采用默认值。 

4、大量的慢请求往往才是502的罪魁祸首,所以请一定要管好你程序中的超时。