PHP-FPM三种运行模式

学习总结

  • static 静态模式,启动的时候创建固定数量的worker 进程,实际请求大于worker进程的时候 包warning

  • ondemand 按需分配模式,启动的时候不会创建worker进程,根据需要创建,释放在idle_timeout之后

    ​ 这样不能及时的释放连接和建立连接需要消耗资源

  • dynamic 动态模式(默认):启动的时候创建指定数量的worker进程,根据情况合理的worker,定期检查worker,关闭闲置连接

PHP-FPM & FastCGI

  1. PHP-FPM(FastCGI Process Manager)是一个PHPFastCGI进程管理器,从其英文名称和定义可以看出,FPM的核心功能就是进程管理。

  2. FastCGI可以理解为一种协议,用于web服务器(nginx、Apache)和处理程序间进行通信,是一种应用层通信协议。

  3. 工作原理大致如下图
    在这里插入图片描述

fpm的基本实现

  1. fpm创建master进程,在master进程中创建work pool并监听socket,然后fork出多个子进程(work),这些work在启动后阻塞fcgi_accept_request()上,各自accept请求,有请求到达后worker开始读取请求数据,读取完成后开始处理然后再返回,在这期间是不会接收其它请求的,也就是说fpm的子进程同时只能响应一个请求只有把这个请求处理完成后才会accept下一个请求。
  2. fpm的master与work进程间不会直接通讯,master通过共享内存获取worker进程的信息,比如worker进程当前状态、已处理请求数等,master要杀死worker进程也是通过发送信号
  3. pm可以同时监听多个端口,每个端口对应一个worker pool,而每个pool下对应多个worker进程,类似nginx中server概念, 在php-fpm.conf中可以配置多个,例如:
    [web1]
    listen:127.0.0.1:9000
    [web2]
    listen:127.0.0.1:9001
    在这里插入图片描述

php生命周期

PHP在web方式中如何改了文件就立即生效的,重要的几个概念:

● sapi: 可以简单的理解为php引擎对外的一个统一接口,使得php可以和外部程序进行交互

● php的生命周期中关键四个调用: MINT -> RINT -> RSHUTDOWN -> MSHUTDOWN

数据初始化(mint)==》请求初始化(rint) ==》编译脚本(rshuntdown) ==》执行代码(mshutdown)

● fpm: fastcgi进程管理器

FPM 流程

fpm通过sapi接口与php进程交互

1.fpm启动会调用各扩展的MINT方法,进行一些数据初始化(长驻内存)

2.每个请求过来,先会执行RINT对单个请求行一个初始化

3.执行php脚本(在没有缓存opcode的情况下,这里的php脚本是动态执行的,所以更新php脚本后,会执行新的php脚本,详情不在这里叙述)

4.执行RSHUTDOWN方法

5.如果你要停止fpm了,才会执行MSHUTDOWN

fpm对每个请求的处理都是一直在在重复执行 2~4步,在第三步中,php的脚本是动态执行的,由于每次都要执行一次php脚本,而每次php脚本都要有一个把php文件翻译成opcode的流程(比较耗时), 于是就产生的opcache工具。

opcache

直接把php翻译后的opcode代码树保存到共享内存中,以便直接使用,从而减少每次都把php翻译成opcode的开销。

opcache的问题: 按照他的描述,修改了php文件,并不能立即被更新。

opcache的解决方案: 有一个配置来设置隔多长时间检测文件是否更新了,从而有机会在第二步重新来reload相关的文件。

当然,直接reload fpm,从而达到php热更新的效果(opcache扩展可以在第四步把相关的opcode cache给清空)。

work 工作流程

worker的工作流程包含以下几个步骤

  1. 等待请求:fcgi_accept_request()阻塞等待请求
  2. 接收请求:fastcgi请求到达后被worker接收并解析,一直到完全接收,然后将method、query、uri等信息保存到worker进程的fpm_scoreboard_proc_s结构中
  3. 初始化请求:php_request_startup()执行,此步骤会调用每个扩展的PHP_RINIT_FUNCTION方法,初始化一些操作
  4. 处理请求(编译、执行):php代码编译执行阶段,由 php_execute_script方法完成
  5. 关闭请求:返回响应,执行php_request_shutdown方法关闭请求,然后进入第一步继续等待请求,此步骤会执行每个扩展的PHP_RSHUTDOWN_FUNCTION进行一些收尾工作
int main(int argc, char *argv[])
{
    ...变量定义,参数初始化
    //注册SAPI
    sapi_startup(&cgi_sapi_module);
    ...
    //执行php_module_starup()
    if (cgi_sapi_module.startup(&cgi_sapi_module) == FAILURE) {
        return FPM_EXIT_SOFTWARE;
    }
    //初始化
    if(0 > fpm_init(...)){
        //记录日志并退出
        return FPM_EXIT_CONFIG;
    }
    ...
    fpm_is_running = 1;//fpm运行状态标识
    fcgi_fd = fpm_run(&max_requests);//进程初始化,调用fork()创建work进程
     ...
    fcgi_init_request(&request, fcgi_fd); //初始化请求;
    //此阶段的php_request_startup()会调用每个扩展的:PHP_RINIT_FUNCTION();
    if (UNEXPECTED(php_request_startup() == FAILURE)) {
    ...
    }
    ...
    php_fopen_primary_script(&file_handle TSRMLS_CC); //打开脚本; 
    ...
    php_execute_script(&file_handle TSRMLS_CC); //执行脚本; 
    ...
    //worker进程退出
    php_module_shutdown();
    ...
}

PHP-FPM运行三种模式

PHP7 默认是

pm=dynamic
pm.max_children=50

fpm.conf 参数

  • pm.start_servers = 5

    动态方式下的起始php-fpm进程数量

    只有当pm的配置为dynamic时候才会有效如果为static,会忽略此参数

    php启动的时候,开启子进程的数量。具体的子进程的数量会根据请求的变化发生变化,但是最大不会超过pm.max_children配置的数值。

    min_spare_servers + (max_spare_servers - min_spare_servers) / 2;
    一般而言,设置成10-20之间的数据足够满足需求了。

  • pm.max_children = 50

    表示php-fpm能启动的子进程的最大数

    static下只有这一个参数生效

    计算方式

    一般来说一台服务器正常情况下每一个php-cgi所耗费的内存在20M~30M左右,因此我的”max_children”我设置成40个,20M*40=800M也就是说在峰值的时候所有PHP-CGI所耗内存在800M以内,低于我的有效内存2Gb。

    而如果我 的”max_children”设置的较小,比如5-10个,那么php-cgi就会“很累“,处理速度也很慢,等待的时间也较长,占用的CPU也很高。

    如果长时间没有得到处理的请求就会出现 504 Gateway Time-out 这个错误,而正在处理的很累的那几个php-cgi如果遇到了问题就会出现 502 Bad gateway 这个错误。

    max_children较好的设置方式根据req/s(吞吐率,单位时间里服务器处理的最大请求数,单位req/s)来设置,若程序是 100 req/s 的处理能力,那么就设置 100比较好,这是动态来调整的。

  • pm.max_spare_servers

    动态方式下空闲状态最大php-fpm数量

    pm.max_spare_servers的值只能小于等于pm.max_children

    系统会在php-fpm运行开始时启动pm.start_servers个php-fpm进程,然后根据系统的需求动态在pm.min_spare_servers和pm.max_spare_servers之间调整php-fpm进程数。

  • pm.process_idle_timeout = 10s

    worker 空闲多少秒之后被kill,按需分配模式dynamic

    默认10s

  • pm.max_requests=500

    每个进程处理多少个请求之后自动终止,可以有效防止内存溢出,如果为0则不会自动终止,默认为0#设置每个子进程重生之前服务的请求数. 对于可能存在内存泄漏的第三方模块来说是非常有用的. 如果设置为 ‘0’ 则一直接受请求. 等同于 PHP_FCGI_MAX_REQUESTS 环境变量. 默认值: 0

  • pm.status_path

    注册的URI,以展示php-fpm状态的统计信息

    在php-fpm.conf中打开
    在nginx中配置
    server {
        ......
    	
        # 在 server 中添加以下配置
        location = /status {
    	include fastcgi_params;
    	fastcgi_pass 127.0.0.1:9000;
    	fastcgi_param SCRIPT_FILENAME $fastcgi_script_name;
        }
    	
        .....
    }
    

    在这里插入图片描述

    pool – fpm池子名称,大多数为www
    process manager – 进程管理方式,值:static, dynamic or ondemand. dynamic
    start time – 启动日期,如果reload了php-fpm,时间会更新
    start since – 运行时长
    accepted conn – 当前池子接受的请求数
    listen queue – 请求等待队列,如果这个值不为0,那么要增加FPM的进程数量
    max listen queue – 请求等待队列最高的数量
    listen queue len – socket等待队列长度
    idle processes – 空闲进程数量
    active processes – 活跃进程数量
    total processes – 总进程数量
    max active processes – 最大的活跃进程数量(FPM启动开始算)
    max children reached - 进程最大数量限制的次数,如果这个数量不为0,那说明你的最大进程数量太小了,请改大一点。
    slow requests – 启用了php-fpm slow-log,缓慢请求的数量

  • ping.path

    ping url,可以用来测试php-fpm是否存活并可以响应

  • ping.response
    ping url的响应正文返回为 HTTP 200 的 text/plain 格式文本. 默认值: pong.
    ping.response = pong
    11)pid = run/php-fpm.pid
    #pid设置,默认在安装目录中的var/run/php-fpm.pid,建议开启

  • error_log = log/php-fpm.log
    #错误日志,默认在安装目录中的var/log/php-fpm.log

  • log_level = notice
    #错误级别. 可用级别为: alert(必须立即处理), error(错误情况), warning(警告情况), notice(一般重要信息), debug(调试信息). 默认: notice.

  • emergency_restart_threshold = 60
    emergency_restart_interval = 60s
    #表示在emergency_restart_interval所设值内出现SIGSEGV或者SIGBUS错误的php-cgi进程数如果超过 emergency_restart_threshold个,php-fpm就会优雅重启。这两个选项一般保持默认值。

  • process_control_timeout = 0
    #设置子进程接受主进程复用信号的超时时间. 可用单位: s(秒), m(分), h(小时), 或者 d(天) 默认单位: s(秒). 默认值: 0.

  • daemonize = yes
    #后台执行fpm,默认值为yes,如果为了调试可以改为no。在FPM中,可以使用不同的设置来运行多个进程池。 这些设置可以针对每个进程池单独设置。

  • listen = 127.0.0.1:9000
    #fpm监听端口,即nginx中php处理的地址,一般默认值即可。可用格式为: ‘ip:port’, ‘port’, ‘/path/to/unix/socket’. 每个进程池都需要设置.

  • listen.backlog = -1
    #backlog数,-1表示无限制,由操作系统决定,此行注释掉就行。 19)listen.allowed_clients = 127.0.0.1
    #允许访问FastCGI进程的IP,设置any为不限制IP,如果要设置其他主机的nginx也能访问这台FPM进程,listen处要设置成本地可被访问的IP。默认值是any。每个地址是用逗号分隔. 如果没有设置或者为空,则允许任何服务器请求连接
    listen.owner = www
    listen.group = www
    listen.mode = 0666

  • #unix socket设置选项,如果使用tcp方式访问,这里注释即可。
    user = www
    group = www
    #启动进程的帐户和组

  • request_terminate_timeout = 0
    #设置单个请求的超时中止时间. 该选项可能会对php.ini设置中的’max_execution_time’因为某些特殊原因没有中止运行的脚本有用. 设置为 ‘0’ 表示 ‘Off’.当经常出现502错误时可以尝试更改此选项。

  • request_slowlog_timeout = 10s
    #当一个请求该设置的超时时间后,就会将对应的PHP调用堆栈信息完整写入到慢日志中. 设置为 ‘0’ 表示 ‘Off’

  • slowlog = log/$pool.log.slow
    #慢请求的记录日志,配合request_slowlog_timeout使用

  • rlimit_files = 1024
    #设置文件打开描述符的rlimit限制. 默认值: 系统定义值默认可打开句柄是1024,可使用 ulimit -n查看,ulimit -n 2048修改。

  • rlimit_core = 0
    #设置核心rlimit最大限制值. 可用值: ‘unlimited’ 、0或者正整数. 默认值: 系统定义值.

  • chroot =
    #启动时的Chroot目录. 所定义的目录需要是绝对路径. 如果没有设置, 则chroot不被使用.

  • chdir =
    #设置启动目录,启动时会自动Chdir到该目录. 所定义的目录需要是绝对路径. 默认值: 当前目录,或者/目录(chroot时)

  • catch_workers_output = yes
    #重定向运行过程中的stdout和stderr到主要的错误日志文件中. 如果没有设置, stdout 和 stderr 将会根据FastCGI的规则被重定向到 /dev/null . 默认值: 空.

static 静态模式

php-fpm启动的时候,创建固定的worker数量

该模式比较简单,在启动时按照配置pm.max_children启动固定数量的的进程,这些进程阻塞进行请求的接收
方法执行流程:
fpm_run()->fpm_children_create_initial()->fpm_children_make()
启动fpm的时候会调用fpm_run方法,而fpm_run方法内部会调用子进程初始化方法fpm_children_create_initial,
在该方法内部会调用fpm_children_make方法创建worker进程。

php-fpm启动采用固定大小数量的worker,在运行期间也不会扩容,虽然也有1秒的定时器,仅限于统计一些状态信息,例如空闲worker个数,活动worker个数,网络连接队列长度等信息。

配置

pm=static
pm.max_children=3

static的时候只有这个参数生效

在这里插入图片描述
方法执行流程:
fpm_run()->fpm_children_create_initial()->fpm_children_make()
启动fpm的时候会调用fpm_run方法,而fpm_run方法内部会调用子进程初始化方法fpm_children_create_initial,
在该方法内部会调用fpm_children_make方法创建worker进程

原理

img

配置项要求

1、pm.max_children> 0 必须配置,且只有这一个参数生效

优缺点

如果配置成static,只需要考虑max_children的数量,数量取决于cpu的个数和应用的响应时间,我司配置的是50。

我司不考虑动态的增加减少那么十几个或者几十个worker,我们的内存没有紧张到这个程度,所以,我们一步到位,把worker数配置到支持最大流量,(哈哈,50也是随便定的,足矣足矣呢)

ondemand 按需分配模式

pm = ondemand
pm.process_idle_timeout = 60 #worker 空闲多少秒之后被kill,按需分配模式dynamic
pm.max_children=3

在这里插入图片描述

原理

img

ondemand原理图

\1. 从上图可以看出,新建worker的触发条件是连接的到来,而不是实际的请求(例如,只进行连接比如telnet,不发请求数据也会新建worker)

\2. worker的数量受限于pm.max_children配置,同时受限全局配置process.max(准确的说,三种模式都受限于全局配置)

3.1秒定时器作用

找到空闲worker,如果空闲时间超过pm.process_idle_timeout大小,关闭。这个机制可能会关闭所有的worker。

配置项要求

\1. pm.max_children> 0

\2. pm.process_idle_timeout> 0,如果不设置,默认10s

优缺点

优点:按流量需求创建,不浪费系统资源(在硬件如此便宜的时代,这个优点略显鸡肋)

缺点:由于php-fpm是短连接的,所以每次请求都会先建立连接,建立连接的过程必然会触发上图的执行步骤,所以,在大流量的系统上master进程会变得繁忙,占用系统cpu资源,不适合大流量环境的部署

代码流程

ondemand模式,运行流程和第一步相同,不同之处是在第二个函数中不会分配work进程,而是注册了一个事件回调函数fpm_pctl_on_socket_accept(),部分代码如下:

if (wp->config->pm == PM_STYLE_ONDEMAND) { 
 	wp->ondemand_event = (struct fpm_event_s *)malloc(sizeof(struct fpm_event_s));   
  	......   
 	memset(wp->ondemand_event, 0, sizeof(struct fpm_event_s));    
  	fpm_event_set(wp->ondemand_event,wp->listening_socket,FPM_EV_READ|FPM_EV_EDGE,fpm_pctl_on_socket_accept, wp);    
  	......
}

以上代码片段只保留了部分本文所需要的内容

ondemand模式work进程的创建,回调函数fpm_pctl_on_socket_accept()的部分代码如下:

if (wp->running_children >= wp->config->pm_max_children) {   //判断进程数是否超过最大限制
       ......    
       return;
}
for (child = wp->children; child; child = child->next) { 
   //fpm_request_is_idle函数返回return proc->request_stage == FPM_REQUEST_ACCEPTING
   if (fpm_request_is_idle(child)) { 
       return;   // FPM_REQUEST_ACCEPTING代表处于等待请求阶段
 }
}
...... 
fpm_children_make(wp, 1, 1, 1);//创建work进程

ondemand模式work进程的关闭
PFM注册了一个定时事件fpm_pctl_perform_idle_server_maintenance_heartbeat检查当前模式下work进程的运行情况,当空闲进程等待请求时间超过pm_process_idle_timeout后,会对最后一个空闲worker进程发出关闭信号,此操作由主进程进行处理,部分代码如下:

if (wp->config->pm == PM_STYLE_ONDEMAND) {
    struct timeval last, now;    
    if (!last_idle_child) continue;//最后一个idle进程
     ......
    // last.tv_sec为上次接收请求的时间
    if (last.tv_sec < now.tv_sec - wp->config->pm_process_idle_timeout) {
        last_idle_child->idle_kill = 1;
        fpm_pctl_kill(last_idle_child->pid, FPM_PCTL_QUIT);
    }
    continue;
}

dynamic模式:动态模式

dynamic模式,启动时分配固定数量的work进程,然后随着请求的增加会增加进程数,此模式下几个重要的配置项如下:

max_children 最大进程数
pm_max_spare_servers 允许最大的空闲进程数
min_spare_servers 允许最小的空闲进程数
start_servers 启动时的进程数

执行过程和ondemand模式类似,启动时主进程都会创建一个定时事件来定时检查work的运行状况,不同的是dynamic模式初始化的时候会创建一定数量的进程,而ondemand模式不会创建

配置

pm = dynamic
pm.max_children = 3
pm.start.servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 6

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZehrV8gR-1579172905068)(PHP-FPM.assets/image-20200116174736466.png)]

原理

img

dynamic原理图

\1. 1秒定时器作用

检查空闲worker数量,按照一定策略动态调整worker数量,增加或减少。增加时,worker最大数量<=max_children· <=全局process.max;减少时,只有idle >pm.max_spare_servers时才会关闭一个空闲worker。

idle > pm.max_spare_servers,关闭启动时间最长的一个worker,结束本次处理

idle >= pm.max_children,打印WARNING日志,结束本次处理

idle < pm.max_children,计算一个num值,然后启动num个worker,结束本次处理

配置项要求

\1. pm.min_spare_servers/pm.max_spare_servers有效范围(0,pm.max_children]

\2. pm.max_children> 0

\3. pm.min_spare_servers<=pm.max_spare_servers

\4. pm.start_servers有效范围[pm.min_spare_servers,pm.max_spare_servers]如果没有配置,默认pm.min_spare_servers + (pm.max_spare_servers - pm.min_spare_servers) / 2

优缺点

优点:动态扩容,不浪费系统资源,master进程设置的1秒定时器对系统的影响忽略不计;

缺点:如果所有worker都在工作,新的请求到来只能等待master在1秒定时器内再新建一个worker,这时可能最长等待1s;

总结

  • static 静态模式,启动的时候创建固定数量的worker 进程,实际请求大于worker进程的时候 包warning

  • ondemand 按需分配模式,启动的时候不会创建worker进程,根据需要创建,释放在idle_timeout之后

    ​ 这样不能及时的释放连接和建立连接需要消耗资源

  • dynamic 动态模式(默认):启动的时候创建指定数量的worker进程,根据情况合理的worker,定期检查worker,关闭闲置连接

参考文件,如有不合适的,请告知删除

链接:https://www.jianshu.com/p/c9a028c834ff

https://www.toutiao.com/a6776929604788552196/?tt_from=mobile_qq&utm_campaign=client_share&timestamp=1579141890&app=news_article&utm_source=mobile_qq&utm_medium=toutiao_android&req_id=202001161031300100080431041DC7F156&group_id=6776929604788552196

https://blog.csdn.net/njrclj/article/details/85062459

发布了56 篇原创文章 · 获赞 41 · 访问量 7925

猜你喜欢

转载自blog.csdn.net/qq_39787367/article/details/104009809