zoukankan      html  css  js  c++  java
  • 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.

    • 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. 模拟多线程:

     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}
    
    ");
    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)
    ");
    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)。

    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.

  • 相关阅读:
    Memcached简介
    TP5 volist
    Getting command line access to PHP and MySQL running MAMP on OSX
    PHP use
    PHP 命名空间(namespace)
    mac 使用 pf 做端口转发
    微信测试帐号如何设置URL和Token,以及相关验证的原理
    ionic开发笔记
    Eclipse下配置Maven
    css引用第三方字体库
  • 原文地址:https://www.cnblogs.com/liliuguang/p/13070794.html
Copyright © 2011-2022 走看看