php-fpm进程模型与进程池隔离优化
本文主要想记录优化线上502问题,包括采用进程池隔离的手段。借这个事,我们把php-fpm的线程模型梳理下,PHP开发的同学,这是必须要掌握的。
php-fpm 进程模型
php-fpm采用多进程单线程模型,区分master进程和worker进程。先用几句话描述下进程的创建和作用:
php-fpm先创建一个 master进程,然后创建并监听 listen socket,再 fork出多个 worker子进程(worker 进程自然继承了这个listen socket)。master 可以同时监听多个端口,每个端口可对应一个进程池,池里包含多个 worker进程。
master 进程不直接处理用户请求,主要负责管理 worker进程,如发信号等。
每个 worker进程来 accept请求。worker 进程的处理流程:启动后阻塞在 accept上,请求到达后开始读取请求数据,读取完开始处理,然后再返回。在这期间这个worker 进程是不会接收其它请求的,只能一次响应一个请求,只有把这个请求处理完成后才会 accept 下一个请求。
这和nginx的线程模型如出一辙,但IO模型不同,nginx使用了IO复用,一个worker进程可以同时处理多个请求。
ps -ef
work@VM_x_centos:~$ ps -ef --forest | grep -v grep | grep php-fpm
work 2248 1 0 3月27 ? 00:00:02 php-fpm: master process (/usr/local/php/etc/php-fpm.conf)
work 2249 2248 0 3月27 ? 00:00:00 \_ php-fpm: pool soda
work 2250 2248 0 3月27 ? 00:00:00 \_ php-fpm: pool soda
work 2254 2248 0 3月27 ? 00:00:00 \_ php-fpm: pool www
work 2256 2248 0 3月27 ? 00:00:00 \_ php-fpm: pool www
图解
进程模型图
生命周期图
图片来源于文章- PHP-FPM 生命周期
php-fpm 惊群问题解决
前边说到master fork进程时,worker进程继承了listenfd,这就导致一个请求来产生的惊群问题。php-fpm 是在 accept前后加了加锁和解锁,避免惊群效应。如代码:
//fastcgi.c
int fcgi_accept_request(fcgi_request *req)
{
...
// listenfd 加锁
FCGI_LOCK(req->listen_socket);
req->fd = accept(listen_socket, (struct sockaddr *)&sa, &len);
FCGI_UNLOCK(req->listen_socket);
...
}
优化nginx 502问题
之前生产环境出现过,nginx状态码报502的问题,从发现到优化解决这里一并记录下来。生产环境的数据这里不便写,就用自己的机器复现了。
LNMP架构,nginx代理请求给到php cgi程序即php-fpm处理。用户和nginx中间有一个TCP连接,nginx和php-fpm有个TCP连接(单机部署也可以unix socket通信,一个道理)。
如图:
日志
为什么会出现502 bad gateway?不妨先看这个case下,nginx和php-fpm的日志。
nginx log
注意error log中的描述,从 upstream(这里就是指 php-fpm)获取响应时失败,连接断开了。
==> error.log <==
[error] 1810#0: *152 recv() failed (104: Connection reset by peer) while reading response header from upstream, client: 10.13.140.13, server: localhost, request: "GET / HTTP/1.1", upstream: "fastcgi://unix:/tmp/php-fpm.sock:", host: "hansongda.cn"
==> access.log <==
10.13.140.13, - - [] "GET / HTTP/1.1" 502 537 "-" "Mozilla/5.0 Chrome" "-"
php-fpm log
注意php-fpm 日志中,显示脚本执行超时结束了。
[-] WARNING: [pool www] child 1815, script '/data/www/../index.php' (request: "GET /index.php") execution timed out (5.013558 sec), terminating
这就是LNMP架构下一个典型的状态码502的问题。即 php服务处理这次请求超时了(request_terminate_timeout),这个时间小于 nginx设置的和后端服务的交互时间(fastcgi_read_timeout),最后nginx响应给用户就是502。
优化
-
优化长耗时接口
-
调大php-fpm参数 request_terminate_timeout
-
进程池隔离
长耗时接口是统计查询接口,给统计查询API分配一个独立的进程池,隔离影响使之不要影响配置变更的重要API。
nginx.conf
# location匹配API,port1 对应进程池A
location ~ 匹配统计API {
fastcgi_pass IP:port1;
...
fastcgi_connect_timeout 10;
fastcgi_read_timeout 10;
fastcgi_send_timeout 10;
}
# location匹配API,port2 对应进程池B
location ~ 其他API {
fastcgi_pass IP:port2;
...
fastcgi_connect_timeout 10;
fastcgi_read_timeout 10;
fastcgi_send_timeout 10;
}
php-fpm conf
php-fpm.conf 包含各个进程池自己独立的配置
include=/usr/local/php/etc/php-fpm.d/*.conf
poolA.conf
# 根据服务性能,调整一个合适的时间
request_terminate_timeout = 10s