在nginx上跑php容易出现502错误,很多人遇到过这个问题,到底是什么原因导致的呢?
出现502错误后,又有哪些解决办法呢?
本文源起于前段时间处理无线浏览器的一次502报警后总结得来,希望能为大家提供参考。
从报警说起
现象
1、打开静态页面很快,如无线浏览器的更新日志页面,请求云控接口却很慢,报502错误。
2、最开始一周报警1-2次,后来发展到一天报警1次。
处理过程
1、查看错误日志,发现502错误的响应时间都比较长,均为21s左右。见下图:
日志中请求的是云控接口,就是一个读取配置文件然后吐出数据的过程,正常情况下响应时间应该是非常短的。
因此直观猜测是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脚本验证一下,结果如下图:
由于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的罪魁祸首,所以请一定要管好你程序中的超时。