首页  »   PHP

PHP-FPM历程池探秘

网友分享于:2017-11-15  浏览:0次
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         1     1  0.0    1  0.0   4504   692 ?        Ss   13:10   0:00 /bin/sh /usr/local/php/bin/php-fpm start
root         7     7  0.0    1  0.4 176076 19304 ?        Ss   13:10   0:00 php-fpm: master process (/usr/local/php/etc/php-fpm.conf)
www-data     8     8  0.0    1  0.2 176076  8132 ?        S    13:10   0:00 php-fpm: pool www
www-data     9     9  0.0    1  0.2 176076  8132 ?        S    13:10   0:00 php-fpm: pool www
root        10    10  0.0    1  0.0  18376  3476 ?        Ss   14:11   0:00 bash
root        66    66  0.0    1  0.0  34420  2920 ?        R+   15:13   0:00 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.

PHP-FPM探秘手段:模拟多线程并发执行


1. 什么是线程:参考本人此篇 为什么要使用线程 。

2. 模拟多线程

 1 <?php
 2 /**
 3  * PHP 只支持多进程不支持多线程。
 4  *
 5  * PHP-FPM 在进程池中运行多个子进程并发处理所有连接,
 6  * 同一个子进程可先后处理多个连接请求,但同一时间
 7  * 只能处理一个连接请求,未处理连接请求将进入队列等待处理
 8  *
 9  */
10 
11 class SimulatedThread
12 {
13     //模拟线程标识
14     private $threadID;
15 
16     //主机名
17     private $host = 'tcp://172.17.0.5';
18 
19     //端口号
20     private $port = 80;
21 
22     public function __construct()
23     {
24         //采用当前时间给线程编号
25         $this->threadID = microtime(true);
26     }
27 
28     /**
29      * 通过socket发送一个新的HTTP连接请求到本机,
30      * 此时当前模拟线程既是服务端又是模拟客户端
31      *
32      * 当前(程序)子进程sleep(1)后会延迟1s才继续执行,但其持有的连接是继续有效的,
33      * 不能处理新的连接请求,故这种做法会降低进程池处理并发连接请求的能力,
34      * 类似延迟处理还有time_nanosleep()、time_sleep_until()、usleep()。
35      * 而且sleep(1)这种做法并不安全,nginx依然可能出现如下错误:
36      * “epoll_wait() reported that client prematurely closed connection,
37      * so upstream connection is closed too while connecting to upstream”
38      *
39      * @return void
40      */
41     public function simulate()
42     {
43         $run = $_GET['run'] ?? 0;
44         if ($run++ < 9) {//最多模拟10个线程
45             $fp = fsockopen($this->host, $this->port);
46             fputs($fp, "GET {$_SERVER['PHP_SELF']}?run={$run}\r\n\r\n");
47             sleep(1);//usleep(500) 将延迟 500 微妙(us),1 s = 1000000 us
48             fclose($fp);
49         }
50 
51         $this->log();
52     }
53 
54     /**
55      * 日志记录当前模拟线程运行时间
56      *
57      * @return void
58      */
59     private function log()
60     {
61         $fp = fopen('simulated.thread', 'a');
62         fputs($fp, "Log thread {$this->threadID} at " . microtime(true) . "(s)\r\n");
63 
64         fclose($fp);
65     }
66 }
67 
68 $thread = new SimulatedThread();
69 $thread->simulate();
70 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)。

 

从以上的记录可以看出:

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.

 

相关解决方案

最新解决方案