PHP FastCGI进程管理器PHP-FPM的架构
博客专区 > eechen 的博客 > 博客详情
PHP FastCGI进程管理器PHP-FPM的架构
eechen 发表于2年前
PHP FastCGI进程管理器PHP-FPM的架构
  • 发表于 2年前
  • 阅读 10380
  • 收藏 153
  • 点赞 12
  • 评论 49

腾讯云 技术升级10大核心产品年终让利>>>   


一个master进程,支持多个pool,每个pool由master进程监听不同的端口,pool中有多个worker进程.
每个worker进程都内置PHP解释器,并且进程常驻后台,支持prefork动态增加.
每个worker进程支持在运行时编译脚本并在内存中缓存生成的opcode来提升性能.
每个worker进程支持配置响应指定请求数后自动重启,master进程会重启挂掉的worker进程.
每个worker进程能保持一个到MySQL/Memcached/Redis的持久连接,实现"连接池",避免重复建立连接,对程序透明.
使用数据库持久连接时应该设置固定数量的worker进程数,不要使用动态的prefork模式.

@syaokun219@IM鑫爷 纠正,以下两句有误:
master进程采用epoll模型异步接收和分发请求,listen监听端口,epoll_wait等待连接.
然后分发给对应pool里的worker进程,worker进程accept请求后poll处理连接.
应该是:
master进程并不接收和分发请求,而是worker进程直接accept请求后poll处理.
master进程不断调用epoll_wait和getsockopt是用来异步处理信号事件和定时器事件.
这里提一下,Nginx也类似,master进程并不处理请求,而是worker进程直接处理,
不过区别在于Nginx的worker进程是epoll异步处理请求,而PHP-FPM仍然是poll.

如果worker进程不够用,master进程会prefork更多进程,
如果prefork达到了pm.max_children上限,worker进程又全都繁忙,
这时master进程会把请求挂起到连接队列backlog里(默认值是511).

1个PHP-FPM工作进程在同一时刻里只能处理1个请求.
MySQL的最大连接数max_connections默认是151.
只要PHP-FPM工作进程数不超过151,就不会出现连接不上MySQL的情况.
而且正常情况下,也不需要开启那么多的PHP-FPM工作进程,
比如4个PHP-FPM进程就能跑满4个核心的CPU,
那么你开40个PHP-FPM进程也没有任何意义,
只会占用更多的内存,造成更多的CPU上下文切换,性能反而更差.
为了减少每个请求都重复建立和释放连接的开销,可以开启持久连接,
一个PHP-FPM进程保持一个到MySQL的长连接,实现透明的"连接池".

Nginx跟PHP-FPM分开,其实是很好的解耦,PHP-FPM专门负责处理PHP请求,一个页面对应一个PHP请求,
页面中所有静态资源的请求都由Nginx来处理,这样就实现了动静分离,而Nginx最擅长的就是处理高并发.
PHP-FPM是一个多进程的FastCGI服务,类似Apache的prefork的进程模型,
对于只处理PHP请求来说,这种模型是很高效很稳定的.
不像Apache(libphp.so),一个页面,要处理多个请求,包括图片,样式表,JS脚本,PHP脚本等.

php-fpm从5.3开始才进入PHP源代码主干,之前版本没有php-fpm.
那时的spawn-fcgi是一个需要调用php-cgi的FastCGI进程管理器,
另外像Apache的mod_fcgid和IIS的PHP Manager也需要调用php-cgi进程,
但php-fpm则根本不依赖php-cgi,完全独立运行,也不依赖php(cli)命令行解释器.
因为php-fpm是一个内置了php解释器的FastCGI服务,启动时能够自行读取php.ini配置和php-fpm.conf配置.

个人认为,PHP-FPM工作进程数,设置为2倍CPU核心数就足够了.
毕竟,Nginx和MySQL以及系统同样要消耗CPU.
根据服务器内存来设置PHP-FPM进程数非常不合理,
把内存分配给MySQL,Memcached,Redis,Linux磁盘缓存(buffers/cache)这些服务显然更合适.
过多的PHP-FPM进程反而会增加CPU上下文切换的开销.
PHP代码中应该尽量避免curl或者file_get_contents这些可能会产生较长网络I/O耗时的代码.
注意设置CURLOPT_CONNECTTIMEOUT_MS超时时间,避免进程被长时间阻塞.
如果要异步执行耗时较长的任务,可以 pclose(popen('/path/to/task.php &', 'r')); 打开一个进程来处理,
或者借助消息队列,总之就是要尽量避免阻塞到PHP-FPM工作进程.
在php-fpm.conf中把request_slowlog_timeout设为1秒,在slowlog中查看是否有耗时超过1秒的代码.
优化代码,能够为所有PHP-FPM工作进程减负,这个才是提高性能的根本方法.

能让CPU满负荷运行的操作可以视为CPU密集型操作.
curl和下载则是典型的I/O密集型操作,因为耗时主要发生在网络I/O和磁盘I/O.
需要PHP认证的下载操作可以委托为Nginx的AIO线程池:
header("X-Accel-Redirect: $file_path");
至于curl操作,比如可以建立一个监听9001端口的名为upload的PHP-FPM进程池(pool),
专门负责处理curl操作(通过Nginx分发),避免curl操作阻塞到监听9000端口的计算密集的www进程池.
这时upload进程池多开点进程也无所谓.

nginx.conf: 访问curl.php的请求都交给监听9001的PHP-FPM进程池处理
location = /curl.php {
    include fastcgi_params;
    fastcgi_pass 127.0.0.1:9001;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
php-fpm.conf: 正常脚本由静态www池处理,阻塞脚本由动态curl池处理
[www]
listen = 127.0.0.1:9000
pm = static
pm.max_children = 4
[curl]
listen = 127.0.0.1:9001
pm = dynamic
pm.max_children = 8
pm.start_servers = 4
pm.min_spare_servers = 4
pm.max_spare_servers = 4


其中IO密集这个进程池[curl]采用动态的prefork进程,比如这里是繁忙时8个,空闲时4个,灵活地利用内存.
而[www]进程池因为阻塞少,可以根据CPU核心数固定数量,避免产生过多的上下文切换降低系统性能.
利用PHP-FPM提供的池的隔离性,分离计算密集和I/O密集操作,可以减少阻塞对整个PHP应用的影响.

补充:

info.php
<?php
if( isset($_POST['submit']) ) {
    header('Content-Type: text/plain; charset=utf-8');
    //chmod 777 uploads
    move_uploaded_file($_FILES['upload_file']['tmp_name'], 'uploads/'.$_FILES['upload_file']['name']);
    print_r($_FILES['upload_file']);
    exit();
} else {
    header('Content-Type: text/html; charset=utf-8');
}
?>
<!DOCTYPE HTML>
<html>
    <head>
        <meta charset="utf-8">
        <title>PHP文件上传测试</title>
    </head>
    <body>
        <!-- enctype="multipart/form-data" 以二进制格式POST传输数据 -->
        <form action="<?php echo pathinfo(__FILE__)['basename']; ?>" method="POST" enctype="multipart/form-data">
            <div>文件1 <input type="file" name="upload_file" /></div>
            <div><input type="submit" name="submit" value="提交" /></div>
        </form>
    </body>
</html>

Nginx和PHP-FPM的工作进程各自只开1个.
以2KB每秒上传图片:
time trickle -s -u 2 curl \
-F "action=info.php" \
-F "upload_file=@linux.jpeg;type=image/jpeg" \
-F "submit=提交" \
http://www.example.com/app/info.php
sudo netstat -antp|egrep "curl|nginx|fpm"
发现只有nginx和curl处于ESTABLISHED状态,nginx和fpm都没有被阻塞.
top -p 4075 可见Nginx单线程.
sudo strace -p 4075 可见Nginx调用recvfrom接收数据并且pwrite保存数据.
sudo strace -p 13751 可见PHP-FPM是在Nginx接收完成用户上传的数据时才获取数据.
既然如此,我设想的另开PHP-FPM进程池处理上传操作的用处就不是太大了.
在文件上传过程中PHP-FPM并不会被阻塞,因为Nginx接收完上传的内容后才一次性交给PHP-FPM.
附:以2KB每秒下载图片
time trickle -s -d 2 \
wget http://www.example.com/app/uploads/linux.jpeg -O /dev/null

 

共有 人打赏支持
eechen
粉丝 928
博文 106
码字总数 55593
作品 1
评论 (49)
Pader
不错
小紫羽
终于这出点靠谱的东西咯
purple_grape
根据服务器内存来设置PHP-FPM进程数非常不合理
把内存分配给MySQL,Memcached,Redis,Linux磁盘缓存(buffers/cache)这些服务显然更合适.

这句话值得商榷吧。web服务器都是纯nginx+php-fpm,不按照内存分配就是浪费资源啊
数据层的mysql至少会剥离web服务器单独存在。
狂飙的小蜗牛
这是哪个版本的Linux,终端是啥? 赶脚挺好看的
沉默的幻想师
非常好 一直想了解的。
eechen

引用来自“狂飙的小蜗牛”的评论

这是哪个版本的Linux,终端是啥? 赶脚挺好看的
Xubuntu 14.04,终端是Xfce桌面自带的xfce4-terminal http://static.oschina.net/uploads/space/2015/1225/194831_Nw68_561214.png
走起来
如果请求过多, 或者等待其它io操作, PHP-FPM工作进程数过少的话, 不够用吧
Pader
php-fpm 的进程数量并不是根据CPU内核的数量来决定的,你也知道一个 fpm 进程同时只能处理一个请求,php-fpm 开多少纯粹是在并发数和资源占用之间的权衡的结果,按照你说的一个 4 CPU 的机器,瞬间并发只能为 4 那是多么的悲哀。

如果因为CPU的上下文切换而放弃更多的进程,这种想法是完全错误的。开更多的进程反而是为了更合理的使用 CPU。

我们公司每天几千万的请求处理,一台虚拟机给4到8个核,开 php-fpm 一两百个都是很正常的现象。如果只给4到8个fpm进程,CPU 是一直在跑,可是请求都得排着队进来,人稍微一多就完全卡在那。这合理吗?
eechen

引用来自“purple_grape”的评论

根据服务器内存来设置PHP-FPM进程数非常不合理
把内存分配给MySQL,Memcached,Redis,Linux磁盘缓存(buffers/cache)这些服务显然更合适.

这句话值得商榷吧。web服务器都是纯nginx+php-fpm,不按照内存分配就是浪费资源啊
数据层的mysql至少会剥离web服务器单独存在。
假设你有一个超市,你的超市有4个收银台(4个CPU核心),你请4名店员(4个FPM进程), 假设一个店员月薪3K(这里相当于一个FPM进程占用的内存),一个月就得支付4名店员12K的薪资.你不能因为闲钱多(内存多),就非得请40名店员来管4个收银台,支出增大了10倍,但效率并不会提高.对CPU来说,为了公平照顾到各个进程,会频繁地切换,而这些切换是有开销的.另外磁盘缓存(buffers/cached)同样需要占用内存,也就是说,操作系统会把访问过的文件尽量保存在内存中.
eechen

引用来自“Pader”的评论

php-fpm 的进程数量并不是根据CPU内核的数量来决定的,你也知道一个 fpm 进程同时只能处理一个请求,php-fpm 开多少纯粹是在并发数和资源占用之间的权衡的结果,按照你说的一个 4 CPU 的机器,瞬间并发只能为 4 那是多么的悲哀。

如果因为CPU的上下文切换而放弃更多的进程,这种想法是完全错误的。开更多的进程反而是为了更合理的使用 CPU。

我们公司每天几千万的请求处理,一台虚拟机给4到8个核,开 php-fpm 一两百个都是很正常的现象。如果只给4到8个fpm进程,CPU 是一直在跑,可是请求都得排着队进来,人稍微一多就完全卡在那。这合理吗?
一个CPU核心,同一时刻只能处理一个任务. 一个FPM进程,同一时刻只能处理一个请求. 只要这个FPM进程不被I/O阻塞,就能保证这个FPM进程能够充分利用一个CPU核心. 所以文末我还提到了利用PHP-FPM池的隔离性,分离I/O密集和计算密集操作,把I/O密集通过Nginx分发到特定的池进行处理,这个池完全可以多开进程,同时采用prefork的模式减少内存占用.
qzlaobi
楼主你这文章完全没在生产上实践啊 像8楼说的4cpu并发只能4 后面的处理全部在队列 还不如频繁切换来得效率
且9楼的比喻完全有问题 收银台不能共用 cpu完全没这问题 怎么可以混为一谈
乌龟壳
开多少进程要看实际的业务需要,如果业务里阻塞的内容比较多,比如较长时间的数据库查询或者webservice调用,那就必须多开一些提高并行度,只有在计算时间比较多的时候,才适合仅开几个。

不过话说回来php-fpm可以根据负载动态调整进程数,这个只要设定一个合理的范围就可以了
purple_grape

引用来自“purple_grape”的评论

根据服务器内存来设置PHP-FPM进程数非常不合理
把内存分配给MySQL,Memcached,Redis,Linux磁盘缓存(buffers/cache)这些服务显然更合适.

这句话值得商榷吧。web服务器都是纯nginx+php-fpm,不按照内存分配就是浪费资源啊
数据层的mysql至少会剥离web服务器单独存在。

引用来自“eechen”的评论

假设你有一个超市,你的超市有4个收银台(4个CPU核心),你请4名店员(4个FPM进程), 假设一个店员月薪3K(这里相当于一个FPM进程占用的内存),一个月就得支付4名店员12K的薪资.你不能因为闲钱多(内存多),就非得请40名店员来管4个收银台,支出增大了10倍,但效率并不会提高.对CPU来说,为了公平照顾到各个进程,会频繁地切换,而这些切换是有开销的.另外磁盘缓存(buffers/cached)同样需要占用内存,也就是说,操作系统会把访问过的文件尽量保存在内存中.
进程的内存优先级肯定是高于磁盘缓存的,在进程面前强调内存缓存的重要性有必要吗? 进程用剩的,才给缓存好不?!
eechen

引用来自“乌龟壳”的评论

开多少进程要看实际的业务需要,如果业务里阻塞的内容比较多,比如较长时间的数据库查询或者webservice调用,那就必须多开一些提高并行度,只有在计算时间比较多的时候,才适合仅开几个。

不过话说回来php-fpm可以根据负载动态调整进程数,这个只要设定一个合理的范围就可以了
文末已经提到了,专门设一个存在I/O阻塞的进程池,采用dynamic动态prefork的模式节省内存占用,分离计算密集和I/O密集操作,可以减少阻塞对整个PHP应用的影响。
木川瓦兹
作为一个PHPer 完全没看懂,表示有点惭愧
呵大官人
master进程采用epoll模型异步接收和分发请求,listen监听端口,epoll_wait等待连接,

master进程不accept吧,我记得是所有子进程继承master的listenfd,然后各自accept。
乌龟壳

引用来自“乌龟壳”的评论

开多少进程要看实际的业务需要,如果业务里阻塞的内容比较多,比如较长时间的数据库查询或者webservice调用,那就必须多开一些提高并行度,只有在计算时间比较多的时候,才适合仅开几个。

不过话说回来php-fpm可以根据负载动态调整进程数,这个只要设定一个合理的范围就可以了

引用来自“eechen”的评论

文末已经提到了,专门设一个存在I/O阻塞的进程池,采用dynamic动态prefork的模式节省内存占用,分离计算密集和I/O密集操作,可以减少阻塞对整个PHP应用的影响。
为什么要分离?这样增加了管理难度啊
Jun_seba

引用来自“乌龟壳”的评论

开多少进程要看实际的业务需要,如果业务里阻塞的内容比较多,比如较长时间的数据库查询或者webservice调用,那就必须多开一些提高并行度,只有在计算时间比较多的时候,才适合仅开几个。

不过话说回来php-fpm可以根据负载动态调整进程数,这个只要设定一个合理的范围就可以了

引用来自“eechen”的评论

文末已经提到了,专门设一个存在I/O阻塞的进程池,采用dynamic动态prefork的模式节省内存占用,分离计算密集和I/O密集操作,可以减少阻塞对整个PHP应用的影响。

引用来自“乌龟壳”的评论

为什么要分离?这样增加了管理难度啊
而且这个很难分离,除了上传,还有很多I/O密集型操作,读取数据库,读取redis、memcache缓存等等一系列都是IO方面的操作,而且现在的一些主流框架很大部分的瓶颈性能都在IO这块,你怎么能保证能分得那么清,楼主说的情况还是太理想。
添加软件
好文!
Pader
现实是没有绝大部分的请求都不是纯粹的 CPU 与内存处理,其中难免有磁盘IO,数据库存取,一些接口请求,甚至和其它进程或者端口通信,直接开更多 fpm 进程比你说的分发到别的地方进行异步处理最好是在某些争对性的处理中使用,绝对不是可行的通用方法,成本也高出太多。
×
eechen
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: