PHP-FPM学习

    技术2022-07-11  98

    CGI

    common gateway interface (公共网关接口)

    请求模式:

    Web Brower(浏览器) ----(通过http协议传输)----> Http Server(服务器nginx/apache) -----> CGI Program -----> Db

    Server 与 CGI 通过 STDIN/STDOUT(标准的输入/输出)进行数据传递 nginx(动态加载模块) apache(指定加载模块)

    CGI工作原理

    每当客户请求CGI的时候,WEB服务器就请求操作系统生成一个新的CGI解释器进程(如php-cgi.exe), CGI 的一个进程则处理完一个请求后退出,下一个请求来时再创建新进程。

    当然,这样在访问量很少没有并发的情况也行。可是当访问量增大,并发存在,这种方式就不 适合了。于是就有了fastcgi。

    FastCGI

    像是一个常驻(long-live)型的CGI,它可以一直执行着,只要激活后, 不会每次都要花费时间去fork一次(这是CGI最为人诟病的fork-and-execute 模式)。

    工作流程

    Web Server启动时载入FastCGI进程管理器(IIS ISAPI或Apache Module) FastCGI进程管理器自身初始化,启动多个CGI解释器进程(可见多个php-cgi)并等待来自Web Server的连接。 当客户端请求到达Web Server时,FastCGI进程管理器选择并连接到一个CGI解释器。 Web server将CGI环境变量和标准输入发送到FastCGI子进程php-cgi。 FastCGI 子进程完成处理后将标准输出和错误信息从同一连接返回Web Server。 当FastCGI子进程关闭连接时, 请求便告处理完成。 FastCGI子进程接着等待并处理来自FastCGI进程管理器(运行在Web Server中)的下一个连接。 在CGI模式中,php-cgi在此便退出了。

    php-fpm

    php-fpm是 FastCGI 的实现,并提供了进程管理的功能,即php-Fastcgi Process Manager。 进程包含 master 进程和 worker 进程两种进程。

    master 进程只有一个,负责监听端口,接收来自 Web Server 的请求, worker 进程则一般有多个(具体数量根据实际需要配置),

    每个进程内部都嵌入了一个 PHP 解释器,是 PHP 代码真正执行的地方。

    基本实现

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

    大致原理如图:

    Worker工作流程

    等待请求:fcgi_accept_request()阻塞等待请求 接收请求:fastcgi请求到达后被worker接收并解析,一直到完全接收,然后将method、query、uri等信息保存到worker进程的fpm_scoreboard_proc_s结构中 初始化请求:php_request_startup()执行,此步骤会调用每个扩展的PHP_RINIT_FUNCTION方法,初始化一些操作 处理请求(编译、执行):php代码编译执行阶段,由 php_execute_script方法完成 关闭请求:返回响应,执行php_request_shutdown方法关闭请求,然后进入第一步继续等待请求,此步骤会执行每个扩展的PHP_RSHUTDOWN_FUNCTION进行一些收尾工作

    请求步骤

    三种模式

    static模式:静态模式

    该模式比较简单,在启动时按照配置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进程。

    ondemand 模式:按需分配模式

    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模式不会创建,部分代码如下:

    if (idle > wp->config->pm_max_spare_servers && last_idle_child) {//空闲进程数大于配置的允许空闲最大进程数,则关闭进程 last_idle_child->idle_kill = 1; fpm_pctl_kill(last_idle_child->pid, FPM_PCTL_QUIT); ...... } if (idle < wp->config->pm_min_spare_servers) {//空闲进程数小于配置允许的最小进程数,则创建进程 if (wp->running_children >= wp->config->pm_max_children) {//如果达到最大上限,则不再创建 ...... continue; } ...... //此处计算出需要扩充的进程数,从wp->idle_spawn_rate, wp->config->pm_min_spare_servers – idle, wp->config->pm_max_children - wp->running_children三个中选出最小的一个作为本次要扩充的进程数进行扩充 if (wp->idle_spawn_rate >= 8) { zlog(ZLOG_WARNING, "[pool %s] seems busy (you may need to increase pm.start_servers, or pm.min/max_spare_servers), spawning %d children, there are %d idle, and %d total children", wp->config->name, wp->idle_spawn_rate, idle, wp->running_children); } if (wp->idle_spawn_rate < FPM_MAX_SPAWN_RATE) { wp->idle_spawn_rate *= 2;//当前进程分配基数小于配置值时候,会以2的倍数进行增长 } ...... }

    总结

    static模式最简单,但是灵活性不够高 ondemand模式相对static模式比较复杂,会根据请求量的增加动态增加,但是处理完请求后不会立即释放,而是由定时事件定时的检测空闲到一定时间的进程才会释放 dynamic模式类似于ondemand模式,但进程的回收机制不同于ondemand模式,会根据idle数量进行增加和减少worker数量

    三种运行方式各有自己的优势,用哪种方式更合适,要根据自己的业务场景,选择合适的运行方式

    参考:https://zhuanlan.zhihu.com/p/96911584 参考:https://blog.csdn.net/njrclj/article/details/85062459

    Processed: 0.014, SQL: 9