PHP-FPM进程池探秘
PHP 7.2以前的版本只支持多进程而不支持多线程;PHP 7.2+ pthreads 扩展提供了Thread、Worker、Threaded 对象,使得创建、读取、写入以及执行多线程成为可能,并可以在多个线程之间进行同步控制;pthreads 多线程开发也仅限于命令行模式,不能用于 web 服务器环境中。
PHP-FPM 在进程池中运行多个子进程并发处理所有连接请求。通过 ps 查看PHP-FPM进程池(pm.start_servers = 2)状态如下:
root@d856fd02d2fe:~# ps aux -L
USER PID LWP %CPU NLWP %MEM VSZ RSS TTY STAT START TIME COMMAND
root 0.0 0.0 ? Ss : : /bin/sh /usr/local/php/bin/php-fpm start
root 0.0 0.4 ? Ss : : php-fpm: master process (/usr/local/php/etc/php-fpm.conf)
www-data 0.0 0.2 ? S : : php-fpm: pool www
www-data 0.0 0.2 ? S : : php-fpm: pool www
root 0.0 0.0 ? Ss : : bash
root 0.0 0.0 ? R+ : : ps aux -L
从列表中可以看出,进程池www中有两个尚处于空闲状态的子进程PID 8和 PID 9。注:NLWP指轻量级进程数量,即线程数量。
为什么需要PHP-FPM(FastCGI Process Manager)?
FastCGI is a kind of CGI which is long-live, which will always be running.
PHP-CGI is one kind of the Process Manager of FastCGI, which is within php itself.After changing php.ini, you should reboot PHP-CGI to make the new php.ini work.When a PHP-CGI process is killed, all the PHP code will cannot run.
PHP-FPM is another kind of the Process Manager of FastCGI.PHP-FPM can be used to control sub processes of PHP-CGI.
- FastCGI是语言无关的、可伸缩架构的CGI开放扩展,其主要行为是将CGI解释器进程一直保持在内存,不是fork-and-execute,并因此获得较高的性能。FastCGI支持分布式部署,可以部署在WEB服务器以外的多个主机上。
- PHP-CGI作为PHP自带的PHP FastCGI管理器对FastCGI的管理方式简单,也不够灵活高效。
- PHP-FPM为了解决PHP-CGI的不足,为PHP FastCGI提供了一种新的进程管理方式,可以有效控制进程,平滑重载PHP配置,其master process是常驻内存的,worker process有static、dynamic、ondemand三种管理方式。PHP-FPM进程池中的CGI在接受并处理完pm.max_requests个用户请求后将会respawn,并不会保证单个CGI是long-live and always be running,而会以更加灵活高效的方式来保证客户端的连接请求可以被多个CGI处理。
static - a fixed number (pm.max_children) of child processes;
dynamic - the number of child processes are set dynamically based on the
following directives. With this process management, there will be
always at least 1 children.
pm.max_children - the maximum number of children that can
be alive at the same time.
pm.start_servers - the number of children created on startup.
pm.min_spare_servers - the minimum number of children in 'idle'
state (waiting to process). If the number
of 'idle' processes is less than this
number then some children will be created.
pm.max_spare_servers - the maximum number of children in 'idle'
state (waiting to process). If the number
of 'idle' processes is greater than this
number then some children will be killed.
ondemand - no children are created at startup. Children will be forked when
new requests will connect. The following parameter are used:
pm.max_children - the maximum number of children that
can be alive at the same time.
pm.process_idle_timeout - The number of seconds after which
an idle process will be killed.
PHP-FPM探秘手段:模拟多线程并发执行
1. 什么是线程:参考本人此篇 为什么要使用线程 。
2. 模拟多线程:
<?php
/**
* PHP 只支持多进程不支持多线程。
*
* PHP-FPM 在进程池中运行多个子进程并发处理所有请求,
* 同一个子进程可先后处理多个请求,但同一时间
* 只能处理一个请求,未处理请求将进入队列等待处理
*
*/ class SimulatedThread
{
//模拟线程标识
private $threadID; //主机名
private $host = 'tcp://172.17.0.5'; //端口号
private $port = 80; public function __construct()
{
//采用当前时间给线程编号
$this->threadID = microtime(true);
} /**
* 通过socket发送一个新的HTTP连接请求到本机,
* 此时当前模拟线程既是服务端又是模拟客户端
*
* 当前(程序)子进程sleep(1)后会延迟1s才继续执行,但其持有的请求是继续有效的,
* 不能处理新的请求,故这种做法会降低进程池处理并发多个请求的能力,
* 类似延迟处理还有time_nanosleep()、time_sleep_until()、usleep()。
* 而且sleep(1)这种做法并不安全,nginx依然可能出现如下错误:
* “epoll_wait() reported that client prematurely closed connection,
* so upstream connection is closed too while connecting to upstream”
*
* @return void
*/
public function simulate()
{
$run = $_GET['run'] ?? 0;
if ($run++ < 9) {//最多模拟10个线程
$fp = fsockopen($this->host, $this->port);
fputs($fp, "GET {$_SERVER['PHP_SELF']}?run={$run}\r\n\r\n");
sleep(1);//usleep(500) 将延迟 500 微妙(us),1 s = 1000000 us
fclose($fp);
} $this->log();
} /**
* 日志记录当前模拟线程运行时间
*
* @return void
*/
private function log()
{
$fp = fopen('simulated.thread', 'a');
fputs($fp, "Log thread {$this->threadID} at " . microtime(true) . "(s)\r\n"); fclose($fp);
}
} $thread = new SimulatedThread();
$thread->simulate();
echo "Started to simulate threads...";
PHP-FPM探秘汇总:本人通过运行上述脚本后,发现一些可预料但却不是我曾想到的结果
1. PHP-FPM配置项pm.max_children = 5,执行sleep(1)延迟,模拟线程数10,simulated.thread记录如下:
Log thread 1508054181.4236 at 1508054182.4244(s)
Log thread 1508054181.4248 at 1508054182.4254(s)
Log thread 1508054181.426 at 1508054182.428(s)
Log thread 1508054181.6095 at 1508054182.6104(s)
Log thread 1508054182.4254 at 1508054183.4262(s)
Log thread 1508054183.4272 at 1508054183.4272(s)
Log thread 1508054182.4269 at 1508054183.4275(s)
Log thread 1508054182.4289 at 1508054183.43(s)
Log thread 1508054182.6085 at 1508054183.6091(s)
Log thread 1508054182.611 at 1508054183.6118(s)
最新生成的(模拟)线程登记出现在红色标示条目位置是因为进程池的并发连接处理能力上限为5,因此它只可能出现在第六条以后的位置。记录的时间跨度 1508054183.6118 - 1508054181.4236 = 2.1882(s)。下面是同等条件下的另一次测试结果:
Log thread 1508058075.042 at 1508058076.0428(s)
Log thread 1508058075.0432 at 1508058076.0439(s)
Log thread 1508058075.0443 at 1508058076.045(s)
Log thread 1508058075.6623 at 1508058076.6634(s)
Log thread 1508058076.0447 at 1508058077.0455(s)
Log thread 1508058076.046 at 1508058077.0466(s)
Log thread 1508058077.0465 at 1508058077.0466(s)
Log thread 1508058076.0469 at 1508058077.0474(s)
Log thread 1508058076.6647 at 1508058077.6659(s)
Log thread 1508058076.6664 at 1508058077.6671(s)
有意思的是绿色条目代表的(模拟)线程和红色条目代表的(模拟)线程的登记时间是一样的,说明两个(模拟)线程是并发执行的。记录的时间跨度 1508058077.6671 - 1508058075.042 = 2.6251(s)。模拟线程数改为51后,simulated.thread记录如下:
Log thread 1508304245.2524 at 1508304246.3104(s)
Log thread 1508304245.3112 at 1508304246.3119(s)
Log thread 1508304245.461 at 1508304246.4619(s)
Log thread 1508304246.3131 at 1508304247.3141(s)
Log thread 1508304246.3432 at 1508304247.3439(s)
...
Log thread 1508304254.4762 at 1508304255.4767(s)
Log thread 1508304255.4768 at 1508304255.4768(s)
Log thread 1508304255.3284 at 1508304256.3292(s)
Log thread 1508304255.3584 at 1508304256.3593(s)
Log thread 1508304255.4757 at 1508304256.4763(s)
红色条目代表的(模拟)线程创建时间最晚。记录的时间跨度 1508304256.4763 - 1508304245.2524 = 11.2239(s)。
2. PHP-FPM配置项pm.max_children = 10,执行sleep(1)延迟,模拟线程数10,simulated.thread记录如下:
Log thread 1508061169.7956 at 1508061170.7963(s)
Log thread 1508061169.7966 at 1508061170.7976(s)
Log thread 1508061169.7978 at 1508061170.7988(s)
Log thread 1508061170.2896 at 1508061171.2901(s)
Log thread 1508061170.7972 at 1508061171.7978(s)
Log thread 1508061171.7984 at 1508061171.7985(s)
Log thread 1508061170.7982 at 1508061171.7986(s)
Log thread 1508061170.7994 at 1508061171.8(s)
Log thread 1508061171.2907 at 1508061172.2912(s)
Log thread 1508061171.2912 at 1508061172.2915(s)
由于服务端并发连接处理能力上限达到10,因此最新生成的(模拟)线程登记可出现在任何位置。记录的时间跨度 1508061172.2915 - 1508061169.7956 = 2.4959(s)。模拟线程数改为51后,simulated.thread记录如下:
Log thread 1508307376.5733 at 1508307377.5741(s)
Log thread 1508307376.5748 at 1508307377.5759(s)
...
Log thread 1508307382.5883 at 1508307383.589(s)
Log thread 1508307383.5898 at 1508307383.5899(s)
Log thread 1508307382.5896 at 1508307383.5904(s)
Log thread 1508307382.708 at 1508307383.7088(s)
Log thread 1508307382.7091 at 1508307383.7095(s)
...
Log thread 1508307382.716 at 1508307383.7166(s)
Log thread 1508307382.7172 at 1508307383.7178(s)
Log thread 1508307383.5883 at 1508307384.5891(s)
红色条目代表的(模拟)线程创建时间最晚。记录的时间跨度 1508307384.5891 - 1508307376.5733 = 8.0158(s)。
3. PHP-FPM配置项pm.max_children = 5,执行usleep(500)延迟,模拟线程数10,simulated.thread记录如下:
Log thread 1508059270.3195 at 1508059270.3206(s)
Log thread 1508059270.3208 at 1508059270.3219(s)
Log thread 1508059270.322 at 1508059270.323(s)
Log thread 1508059270.323 at 1508059270.324(s)
Log thread 1508059270.3244 at 1508059270.3261(s)
Log thread 1508059270.3256 at 1508059270.3271(s)
Log thread 1508059270.3275 at 1508059270.3286(s)
Log thread 1508059270.3288 at 1508059270.3299(s)
Log thread 1508059270.3299 at 1508059270.331(s)
Log thread 1508059270.3313 at 1508059270.3314(s)
可见日志记录顺序与(模拟)线程生成的顺序一致,但除红色标示条目外,其他条目看不出是并发执行的,更像是一个接一个串行顺序执行完的。记录的时间跨度 1508059270.3314 - 1508059270.3195 = 0.0119(s)。
4. PHP-FPM配置项pm.max_children = 5,执行usleep(400)延迟,模拟线程数10,simulated.thread记录如下:
Log thread 1540308253.6403 at 1540308253.6413(s)
Log thread 1540308253.6419 at 1540308253.6427(s)
Log thread 1540308253.6427 at 1540308253.644(s)
Log thread 1540308253.6437 at 1540308253.6449(s)
Log thread 1540308253.6453 at 1540308253.6467(s)
Log thread 1540308253.6464 at 1540308253.6472(s)
很显然,usleep(400)延迟时间内一旦某个模拟线程连接被关闭并执行失败,后续模拟线程将无法生成并执行。
从以上的记录可以看出:
1)这些(模拟)线程是第一次请求执行脚本后就自动生成的,一个(模拟)线程触发另一个(模拟)线程的创建;
2)这些(模拟)线程中有的虽是在同一个子进程空间中产生并运行的,但有先后顺序,即前一个执行完退出后下一个才能创建并运行,再加上这些模拟线程实际上都是以多进程运行的,所以它们并发执行的效率比真正多线程并发执行效率要低。对此要提高并发处理能力的有效途径是增加子进程数量,避免顺序执行,减少连接请求进入队列长时间等待的概率;
3)前后相邻(模拟)线程生成时间间隔很小,几乎是同时产生,或后一个(模拟)线程在前一个(模拟)线程尚未执行结束并退出之前产生,这是并发执行的条件;
4)多个(模拟)线程可以并发执行,也就是说它们模拟了对同一目标任务(这里就是运行日志登记,当然也可以是其他目标任务)的多线程并发处理。只是这里多个(模拟)线程之间是完全独立的,没有共享当前进程资源,但是都拥有对磁盘文件simulated.thread的写操作。
上述第4条说明模拟多线程的基本目标已实现,所以模拟多线程并发的实现是成功的,其最大好处就是可以充分利用进程池并发处理连接请求的能力。PHP-FPM进程池中同一个子进程可先后处理多个请求,但同一时间只能处理一个请求,未处理请求将进入队列等待处理。换句话,同一个子进程不具有并发处理多个请求的能力。
PHP-FPM Pool配置:它允许定义多个池,每个池可定义不同的配置项。以下只是列举了我在探秘过程中还关注过的其他部分配置项
1. listen:The address on which to accept FastCGI requests.它支持TCP Socket和unix socket两种通讯协议。可设置listen = [::]:9000。
2. listen.allowed_clients:List of addresses (IPv4/IPv6) of FastCGI clients which are allowed to connect. 该配置项为逗号分隔的列表,如listen.allowed_clients = 127.0.0.1,172.17.0.5。
3. pm:Choose how the process manager will control the number of child processes. 该配置项设置FPM管理进程池的方式,包括static、dynamic、ondemand三种。
4. pm.max_requests:The number of requests each child process should execute before respawning. This can be useful to work around memory leaks in 3rd party libraries.设置每个子进程处理请求数的上限,对于处理第三方库中的内存泄漏很有用。
5. pm.status_path:The URI to view the FPM status page.
PHP-FPM进程池探秘的更多相关文章
- PHP 优化之php -fpm 进程
一,php-fpm的启动参数 1 2 3 4 5 6 7 8 9 10 11 12 13 #测试php-fpm配置 /usr/local/php/sbin/php-fpm -t /usr/local/ ...
- Swoole_process实现进程池的方法
Swoole 的进程之间有两种通信方式,一种是消息队列(queue),另一种是管道(pipe),对swoole_process 的研究在swoole中显得尤为重要. 预备知识 IO多路复用 swool ...
- python进程池:multiprocessing.pool
本文转至http://www.cnblogs.com/kaituorensheng/p/4465768.html,在其基础上进行了一些小小改动. 在利用Python进行系统管理的时候,特别是同时操作多 ...
- 64位进程池HashCode兼容处理
背景 net旧项目使用32位生成的HashCode,存储到数据库中.迁移到64位上,就需要对HashCode做兼容处理. 解决方案 1:进程池配置支持32位程序. 2:对Hashcode做兼容处理,[ ...
- Linux客户/服务器程序设计范式2——并发服务器(进程池)
引言 让服务器在启动阶段调用fork创建一个子进程池,通过子进程来处理客户端请求.子进程与父进程之间使用socketpair进行通信(为了方便使用sendmsg与recvmsg,如果使用匿名管道,则无 ...
- PYTHON多进程编码结束之进程池POOL
结束昨晚开始的测试. 最后一个POOL. A,使用POOL的返回结果 #coding: utf-8 import multiprocessing import time def func(msg): ...
- python(进程池/线程池)
进程池 import multiprocessing import time def do_calculation(data): print(multiprocessing.current_proce ...
- python进程池剖析(三)
之前文章对python中进程池的原理.数据流以及应用从代码角度做了简单的剖析,现在让我们回头看看标准库中对进程池的实现都有哪些值得我们学习的地方.我们知道,进程池内部由多个线程互相协作,向客户端提供可 ...
- python进程池剖析(二)
之前文章中介绍了python中multiprocessing模块中自带的进程池Pool,并对进程池中的数据结构和各个线程之间的合作关系进行了简单分析,这节来看下客户端如何对向进程池分配任务,并获取结果 ...
随机推荐
- 【2017集美大学1412软工实践_助教博客】团队作业4——第一次项目冲刺(Alpha版本)小组 成绩
第四次团队作业成绩公布 题目 团队作业4: http://www.cnblogs.com/happyzm/p/6722264.html 团队成绩 成绩公示如下: 检查项 会议内容 代码签入 心得体会或 ...
- 201521123093 java 第八周总结
1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结集合与泛型相关内容. 1.2 选做:收集你认为有用的代码片段 1.泛型简介:同一个代码可以被不同的对象重用 2.使用泛型的好处:允许 ...
- 201521123100 《Java程序设计》第6周学习总结
1. 本周学习总结 1.1 面向对象学习暂告一段落,请使用思维导图,以封装.继承.多态为核心概念画一张思维导图,对面向对象思想进行一个总结. 注1:关键词与内容不求多,但概念之间的联系要清晰,内容覆盖 ...
- 201521123100 《Java程序设计》第3周学习总结
1. 本周学习总结 初学面向对象,会学习到很多碎片化的概念与知识.尝试学会使用思维导图将这些碎片化的概念.知识组织起来.请使用纸笔或者下面的工具画出本周学习到的知识点.截图或者拍照上传. 2. 书面作 ...
- 201521123097《Java程序设计》第三周学习总结
1. 本周学习总结 2. 书面作业 1.代码阅读 public class Test1 { private int i = 1;//这行不能修改 private static int j = 2; p ...
- 201521145048《Java程序设计》第14周学习总结
1. 本周学习总结 1.1 以你喜欢式(思维导图或其他)归纳总结多数据库相关内容. 1.数据库的定义:是为了实现一定目的按某种规则组织起来的"数据"的"集合". ...
- 201521123068 《java程序设计》 第13周学习总结
1. 本周学习总结 以你喜欢的方式(思维导图.OneNote或其他)归纳总结多网络相关内容. 2. 书面作业 1.网络基础 1.1 比较ping www.baidu.com与ping cec.jmu. ...
- 201521123066 《Java程序设计》第十四周学习总结
1. 本周学习总结 2. 书面作业 1. MySQL数据库基本操作 建立数据库,将自己的姓名.学号作为一条记录插入.(截图,需出现自己的学号.姓名) 在自己建立的数据库上执行常见SQL语句(截图) - ...
- linux目录结构图
- POJ-3045 Cow Acrobats (C++ 贪心)
Description Farmer John's N (1 <= N <= 50,000) cows (numbered 1..N) are planning to run away a ...