一, 问题现象
二, 项目背景
线上服务器配置: 8 核 16G
PHP-fpm 配置:
- pm = static
- pm.max_children = 500
- ;pm.max_requests = 5000 // 此配置没有打开
三, 内存持续增高原因分析
查看了 PHP-fpm 相关配置后, 发现系统是静态机制, 并且没有配置 max_requests 项, 没有退出机制
PHP 进程长期存在导致内存积压无法释放, 所以内存消耗持续升高
nginx+PHP-fpm 运行原理?[ https://juejin.im/post/58db7d742f301e007e9a00a7 ]
CGI,fast-CGI,PHP-fpm 之间的关系?
我的理解:
1.webserver 最开始只能处理静态请求, 后来出现了动态请求, 比如 PHP 程序, 然而 webserver 是无法处理 PHP 程序的, 这个是时候怎么办?
2. 交给 PHP 解释器来处理动态 PHP 请求
3. 那么 webserver 可以将动态请求交给 PHP 解释器处理, 那么 webserver 如何与 PHP 解释器进行通信呢?[webserver 就是 nginx/apache]
4. 这个时候就出现了 CGI,CGI 协议就是 webserver 和 PHP 解释器进行通信的协议
5.webserver 在每次请求过来都会 fork 一个 CGI 进程进行处理, 处理完成后返回, 如果有 10k 请求, 那么就 fork 10k 个进程, 显然对系统资源很浪费
6. 这时对 CGI 做了优化, 出现了 fast-CGI,fast-CGI 在请求处理完成后不会直接 kill 掉这个进程, 而是继续保留请求下一次请求, 这样一个进程就能处理多个请求, 不用每次都 fork 进程减少了系统资源浪费
7. 而 PHP-fpm 就是 fast-CGI 的实现, 并且提供了进程管理的功能, 包含 master 和 worker 两种类型的进程
8.master 进程只负责监听端口, 接收来自 webserver 的请求, worker 进程有多个, 每个 worker 进程内部都嵌入了一个 PHP 解释器, 是 PHP 代码真正执行的地方
一些 PHP-fpm 参数理解:
- pm = dynamic # 三种类型选择, static/dynamic/ondemand
- pm.max_children = 5 # PHP-fpm 的 worker 进程最大数量
- pm.start_servers = 3 # PHP-fpm 启动时候, 启动的 worker 数量
- pm.min_spare_servers = 2 # PHP-fpm 最小空闲进程数量, 每时每刻最少也有 2 个是空闲的进程
- pm.max_spare_servers = 4 # PHP-fpm 最大的空闲进程数量
- pm.max_requests = 200 # 单个进程处理请求数量达到 200 就会 kill 掉该进程重启, 进程一直存活容易发生内存泄漏
四, 解决方案
通过现在 PHP-fpm 的配置可以得知, PHP-fpm 没有设置 max_requests, 代表进程一直不会退出, 每个请求完成后 PHP-CGI 会回收内存, 但是不会释放给操作系统, 所以大量内存被 PHP-CGI 占用.
官方的解决办法就是降低 PHP_FCGI_MAX_REQUESTS 的值, 对应 PHP-fpm 配置中的 max_requests
所以, 只要重新设置 max_requests 的值, 让进程达到这个值后自动重启释放内存即可
- # PHP-fpm.conf 配置
- pm = static
- pm.max_children = 500
- pm.start.servers = 100
- pm.min_spare_servers = 20
- pm.max_spare_servers = 100
- pm.max_requests = 2000
修改完配置后对 PHP-fpm 进行重启
- // 查询当前 fpm 的 master 进程号
- ps aux|grep PHP-fpm | grep master
- // 平滑重启 fpm,42891 是 master 进程号
- kill -USR2 42891
- // 立即终止 fpm
- kill -QUIT 42891
- // 查看状态
- ps aux|grep PHP-fpm
- // 备注:
PHP 5.3.3 以后的 PHP-fpm 不再支持 PHP-fpm 以前具有的 /usr/local/PHP/sbin/PHP-fpm (start|stop|reload) 等命令, 所以不要再看这种老掉牙的命令了, 需要使用信号控制:
master 进程可以理解以下信号
INT, TERM 立刻终止
QUIT 平滑终止
USR1 重新打开日志文件
USR2 平滑重载所有 worker 进程并重新载入配置和二进制模块
参考: https://www.cnblogs.com/cocoliu/p/8566193.html
来源: https://segmentfault.com/a/1190000023277074