摘要
通过优化 PHP-FPM 进程重启机制, 改善线上服务器 CPU_IDLE 和 MEM_USED 波动的问题, 使服务器资源利用率更加平滑可靠.
背景
外卖交易服务集群报出在监控图上 CPU_IDLE 波动剧烈, 如图所示.
事实上一直以来, 不仅 PU_IDLE 存在一定的波动, MEM_USED 的周期性断崖式下降再回升也早已司空见惯. 那么 CPU_IDLE 与 MEM_UESD 的波动是否存在关联, 追溯这种现象产生的原因, 我们就必须理解 PHP-FPM 进程管理器的机制.
原理
在 PHP5.3.3 版本中, PHP-FPM 正式被官方收编, 作为 FastCGI 管理器, 支持平滑停止启动进程, slow-log, 动态进程, 运行状态等特性.
PHP-FPM 进程管理支持三种方式: static,dynamic,ondemand. 我们选用的是 static 方式, 即 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 重启机制依然生效, 每个请求都会计数, 当计数到达 max_request 之后, cgi 进程会执行 fcgi_finish_request 退出进程, 子进程退出, fpm-master 进程会收到 SIGCHLD 信号, 运行 fpm_children_bury 重启进程, 重启的方式是 fork 一个子进程.
FastCGI 进程通过 unix socket 承接 Nginx 请求, 负载较为均衡, 生产环境流量大, PHP 进程数配置较大, 数以百计的 FastCGI 会在同一时间到达 max_requests 上限而进行重启, 这便造成了 CPU_IDLE 和 MEM_USED 周期性波动.
优化
max_requests 的初衷是为了避免第三方扩展引起的内存泄漏问题, 虽然线上环境使用的扩展经过分析和测试, 并没有严重的内存泄漏问题, 但是由于扩展内部使用的第三方库太多, 并无法完全避免内存泄漏问题, 同时 max_requests 机制很适合 FastCGI 多进程环境, 以较小的代价, 换取内存泄漏的长治久安.
为了避免 CPU_IDLE 和 MEM_USED 周期波动, 同时保持 max_requests 机制, 需要在 PHP-FPM 源码上稍作修改. FastCGI 进程在启动时, 设置 max_requests, 此时只要将 max_requests 配置参数散列开, 使 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 在生产环境的精细优化, 任重而道远.
来源: http://server.51cto.com/News-578220.htm