摘要通过优化PHP-FPM进程重启机制,改善线上服务器CPU_IDLE和MEM_USED波动问题,提高服务器资源利用率更流畅,更可靠。后台外卖交易服务集群在监控图上报CPU_IDLE剧烈波动,如图。事实上,不仅PU_IDLE有一定程度的波动,MEM_USED周期性的断崖式下降然后回升也早已司空见惯。那么CPU_IDLE和MEM_UESD的波动是否有关系,要追溯造成这种现象的原因,就必须了解PHP-FPM进程管理器的机制。原理在PHP5.3.3版本中,PHP-FPM作为FastCGI管理器被正式包含,支持平滑启停进程、慢日志、动态进程、运行状态等特性。PHP-FPM进程管理支持三种方式:static、dynamic、ondemand。我们选择静态方式,即PHP-FPM生成固定数量的FastCGI进程。这种方法比较简单,避免了频繁打开和关闭进程的开销。(在离线虚拟机环境下,进程管理可以配置成ondemand,这样既减少了内存需求,又避免了进程数不足的情况。)回到我们面临的问题,CPU_IDLE和MEM_USED的周期性波动是如何实现的发生。首先,这是所有集群都存在的现象,然后交易服务集群尤为突出。在查看了应用程序(例如日志收集程序和定时脚本)的影响后,想法落在了PHP-FPM的一个关键参数上:max_requests。max_requests参数可以使FastCGI进程在处理一定数量的请求后自动重启,从而避免第三方扩展内存泄漏带来的破坏性影响。打开线上配置,发现外卖交易服务集群配置的参数太小,为1000,导致FastCGI在请求高峰期频繁重启,给CPU造成负担。所以将max_requests参数调整为10000后,CPU_IDLE的性能得到了提升,如图。但是经过观察,发现CPU_IDLE和MEM_USED周期性波动的问题并没有根除,如图。这很容易理解。我们增加了max_requests参数,但是FastCGI重启机制仍然有效。每个请求都将被计算在内。当count达到max_request时,cgi进程会执行fcgi_finish_request退出进程,子进程退出,fpm-master进程收到SIGCHLD信号,运行fpm_children_bury重启进程,重启的方式是fork一个child过程。FastCGI进程通过unixsockets接受Nginx请求,负载比较均衡,生产环境流量大,PHP进程数配置多。数百个FastCGI会同时达到max_requests的上限并重启,从而导致CPU_IDLE和MEM_USED周期性波动。优化max_requests的初衷是为了避免第三方扩展造成的内存泄露。虽然对线上环境使用的扩展进行了分析和测试,没有出现严重的内存泄漏,但是由于扩展内部使用了过多的第三方库,无法完全避免内存泄漏,而max_requests机制非常适合FastCGI多进程环境。可以用很小的代价换取内存泄漏的长期稳定性。为了在保持max_requests机制的同时避免CPU_IDLE和MEM_USED的周期性波动,需要对PHP-FPM源码稍作修改。当FastCGI进程启动时,设置max_requests。这时候只要对max_requests配置参数进行hash,就可以给FastCGI进程配置不同的值来达到效果。具体代码在sapi/fpm/fpm/fpm.c,修改如下:php_mt_srand(GENERATE_SEED());*max_requests=fpm_globals.max_requests+php_mt_rand()&8191;总结已修改并推出,对比效果如下图所示。至此,CPU_IDLE和MEM_USED告别了周期性波动,避免了CPU计算资源的浪涌效应,内存使用数据更加真实可靠。有了这篇文章,PHP-FPM在生产环境的精细优化还有很长的路要走。
