首頁 後端開發 php教程 Fpm啟動機制及流程的詳細分析(附程式碼)

Fpm啟動機制及流程的詳細分析(附程式碼)

Mar 02, 2019 am 10:04 AM
fpm

這篇文章帶給大家的內容是關於Fpm啟動機制及流程的詳細分析(附程式碼),有一定的參考價值,有需要的朋友可以參考一下,希望對你有幫助。

FPM(FastCGI Process Manager)是PHP FastCGI運作模式的一個行程管理器,從它的定義可以看出,FPM的核心功能是進程管理,那麼它用來管理什麼進程呢?這個問題就需要從FastCGI說起了。

FastCGI是Web伺服器(如:Nginx、Apache)和處理程序之間的一種通訊協議,它是與Http類似的一種應用層通訊協議,注意:它只是一種協定!

前面曾經一再強調,PHP只是一個腳本解析器,你可以把它理解為一個普通的函數,輸入是PHP腳本。輸出是執行結果,假如我們想用PHP代替shell,在命令列中執行一個文件,那麼就可以寫一個程式來嵌入PHP解析器,這就是cli模式,這種模式下PHP就是普通的一個命令工具。接著我們又想:能不能讓PHP處理http請求呢?這時就牽涉到了網路處理,PHP需要接收請求、解析協議,然後處理完成回傳請求。在網路應用場景下,PHP並沒有像Golang那樣實現http網路庫,而是實現了FastCGI協議,然後與web伺服器配合實現了http的處理,web伺服器來處理http請求,然後將解析的結果再通過FastCGI協定轉發給處理程序,處理程序處理完成後將結果傳回web伺服器,web伺服器再傳回用戶,如下圖所示。

PHP實現了FastCGI協定的解析,但是並沒有具體實現網絡處理,一般的處理模型:多進程、多線程,多進程模型通常是主進程只負責管理子進程,而基本的網絡事件由各個子程序處理,nginx、fpm就是這種模式;另一種多執行緒模型與多行程類似,只是它是執行緒粒度,通常會由主執行緒監聽、接收請求,然後交由子執行緒處理,memcached就是這種模式,有的也是採用多進程那種模式:主執行緒只負責管理子執行緒不處理網路事件,各個子執行緒監聽、接收、處理請求,memcached使用udp協定時採用的是這種模式。

1.3.2 基本實作
概括來說,fpm的實作就是創建一個master進程,在master進程中創建並監聽socket,然後fork出多個子進程,這些子進程各自accept請求,子進程的處理非常簡單,它在啟動後阻塞在accept上,有請求到達後開始讀取請求數據,讀取完成後開始處理然後再返回,在這期間是不會接收其它請求的,也就是說fpm的子程序同時只能回應一個請求,只有把這個請求處理完成後才會accept下一個請求,這一點與nginx的事件驅動有很大的區別,nginx的子程序通過epoll管理套接字,如果一個請求資料尚未發送完成則會處理下一個請求,即一個進程會同時連接多個請求,它是非阻塞的模型,只處理活躍的套接字。

fpm的master進程與worker進程之間不會直接進行通信,master透過共享記憶體獲取worker進程的信息,例如worker進程當前狀態、已處理請求數等,當master進程要殺掉一個worker進程時則透過發送訊號的方式通知worker進程。

fpm可以同時監聽多個端口,每個端口對應一個worker pool,而每個pool下對應多個worker進程,類似nginx中server概念。

在php-fpm.conf中透過[pool name]宣告一個worker pool:

[web1]
listen = 127.0.0.1:9000
...

[web2]
listen = 127.0.0.1:9001
...
啟動fpm後查看進程:ps -aux|grep fpm

#root 27155 0.0 0.1 144704 2720 ? Ss 15:16 0:00 php-fpm: master process (/usr/local/php7/etc/php-fpm.conf)
nobody 27156 0.0 0.1 144676 2416 ? S 15:16 0:00 php-fpm : pool web1
nobody 27157 0.0 0.1 144676 2416 ? S 15:16 0:00 php-fpm: pool web1
nobody 27159 0.0 0.1 144680 ##nobody 27159 0.0 0.1 144680 233f ##nobody 27160 0.0 0.1 144680 2376 ? S 15:16 0:00 php-fpm: pool web2
#具體實作上worker pool透過fpm_worker_pool_s這個結構表示,多個worker pool#具體實作上worker pool透過fpm_worker_pool_s這個結構表示,多個worker pool組成一個單鍊錶:#rrrpool ##1.3.3 FPM的初始化
接下來看下fpm的啟動流程,從main()函數開始:

struct fpm_worker_pool_s {
struct fpm_worker_pool_s next; //指向下一个worker pool
struct fpm_worker_pool_config_s config; //conf配置:pm、max_children、start_servers...
int listening_socket; //监听的套接字
...
//以下这个值用于master定时检查、记录worker数
struct fpm_child_s *children; //当前pool的worker链表
int running_children; //当前pool的worker运行总数
int idle_spawn_rate;
int warn_max_children;

struct fpm_scoreboard_s *scoreboard; //记录worker的运行信息,比如空闲、忙碌worker数
...
}
登入後複製

fpm_init()主要有以下幾個關鍵操作:

#(1)fpm_conf_init_main():

解析php-fpm.conf設定文件,分配worker pool記憶體結構並儲存到全域變數:fpm_worker_all_pools,各worker pool設定解析到fpm_worker_pool_s->config。

(2)fpm​​_scoreboard_init_main(): 分配用於記錄worker進程運行資訊的共享內存,按照worker pool的最大worker進程數分配,每個worker pool分配一個fpm_scoreboard_s結構,pool下對應的每個worker進程分配一個fpm_scoreboard_proc_s結構,各結構的對應關係如下圖。

(3)fpm_signals_init_main():

static int sp[2];
int fpm_signals_init_main()
{
struct sigaction act;
//创建一个全双工管道
if (0 > socketpair(AF_UNIX, SOCK_STREAM, 0, sp)) {
    return -1;
}
//注册信号处理handler
act.sa_handler = sig_handler;
sigfillset(&act.sa_mask);
if (0 > sigaction(SIGTERM,  &act, 0) ||
    0 > sigaction(SIGINT,   &act, 0) ||
    0 > sigaction(SIGUSR1,  &act, 0) ||
    0 > sigaction(SIGUSR2,  &act, 0) ||
    0 > sigaction(SIGCHLD,  &act, 0) ||
    0 > sigaction(SIGQUIT,  &act, 0)) {
    return -1;
}
return 0;
}
登入後複製

这里会通过socketpair()创建一个管道,这个管道并不是用于master与worker进程通信的,它只在master进程中使用,具体用途在稍后介绍event事件处理时再作说明。另外设置master的信号处理handler,当master收到SIGTERM、SIGINT、SIGUSR1、SIGUSR2、SIGCHLD、SIGQUIT这些信号时将调用sig_handler()处理:

static void sig_handler(int signo)
{
static const char sig_chars[NSIG + 1] = {
[SIGTERM] = 'T',
[SIGINT] = 'I',
[SIGUSR1] = '1',
[SIGUSR2] = '2',
[SIGQUIT] = 'Q',
[SIGCHLD] = 'C'
};
char s;
...
s = sig_chars[signo];
//将信号通知写入管道sp[1]端
write(sp[1], &s, sizeof(s));
...
}
登入後複製

(4)fpm_sockets_init_main()

创建每个worker pool的socket套接字。

(5)fpm_event_init_main():

启动master的事件管理,fpm实现了一个事件管理器用于管理IO、定时事件,其中IO事件通过kqueue、epoll、poll、select等管理,定时事件就是定时器,一定时间后触发某个事件。

在fpm_init()初始化完成后接下来就是最关键的fpm_run()操作了,此环节将fork子进程,启动进程管理器,另外master进程将不会再返回,只有各worker进程会返回,也就是说fpm_run()之后的操作均是worker进程的。

int fpm_run(int max_requests)
{
struct fpm_worker_pool_s wp;
for (wp = fpm_worker_all_pools; wp; wp = wp->next) {
//调用fpm_children_make() fork子进程
is_parent = fpm_children_create_initial(wp);
   if (!is_parent) {
        goto run_child;
    }
}
//master进程将进入event循环,不再往下走
fpm_event_loop(0);
run_child: //只有worker进程会到这里
*max_requests = fpm_globals.max_requests;
return fpm_globals.listening_socket; //返回监听的套接字
}
登入後複製

在fork后worker进程返回了监听的套接字继续main()后面的处理,而master将永远阻塞在fpm_event_loop(),接下来分别介绍master、worker进程的后续操作。

1.3.4 请求处理
fpm_run()执行后将fork出worker进程,worker进程返回main()中继续向下执行,后面的流程就是worker进程不断accept请求,然后执行PHP脚本并返回。整体流程如下:

(1)等待请求: worker进程阻塞在fcgi_accept_request()等待请求;
(2)解析请求: fastcgi请求到达后被worker接收,然后开始接收并解析请求数据,直到request数据完全到达;
(3)请求初始化: 执行php_request_startup(),此阶段会调用每个扩展的:PHP_RINIT_FUNCTION();
(4)编译、执行: 由php_execute_script()完成PHP脚本的编译、执行;
(5)关闭请求: 请求完成后执行php_request_shutdown(),此阶段会调用每个扩展的:PHP_RSHUTDOWN_FUNCTION(),然后进入步骤(1)等待下一个请求。

int main(int argc, char *argv[])
{
...
fcgi_fd = fpm_run(&max_requests);
parent = 0;
//初始化fastcgi请求
request = fpm_init_request(fcgi_fd);

//worker进程将阻塞在这,等待请求
while (EXPECTED(fcgi_accept_request(request) >= 0)) {
    SG(server_context) = (void *) request;
    init_request_info();
    
    //请求开始
    if (UNEXPECTED(php_request_startup() == FAILURE)) {
        ...
    }
    ...

    fpm_request_executing();
    //编译、执行PHP脚本
    php_execute_script(&file_handle);
    ...
    //请求结束
    php_request_shutdown((void *) 0);
    ...
}
...
//worker进程退出
php_module_shutdown();
...
}
登入後複製

worker进程一次请求的处理被划分为5个阶段:

FPM_REQUEST_ACCEPTING: 等待请求阶段
FPM_REQUEST_READING_HEADERS: 读取fastcgi请求header阶段
FPM_REQUEST_INFO: 获取请求信息阶段,此阶段是将请求的method、query stirng、request uri等信息保存到各worker进程的fpm_scoreboard_proc_s结构中,此操作需要加锁,因为master进程也会操作此结构
FPM_REQUEST_EXECUTING: 执行请求阶段
FPM_REQUEST_END: 没有使用
FPM_REQUEST_FINISHED: 请求处理完成
worker处理到各个阶段时将会把当前阶段更新到fpm_scoreboard_proc_s->request_stage,master进程正是通过这个标识判断worker进程是否空闲的。

1.3.5 进程管理
这一节我们来看下master是如何管理worker进程的,首先介绍下三种不同的进程管理方式:

static: 这种方式比较简单,在启动时master按照pm.max_children配置fork出相应数量的worker进程,即worker进程数是固定不变的
dynamic: 动态进程管理,首先在fpm启动时按照pm.start_servers初始化一定数量的worker,运行期间如果master发现空闲worker数低于pm.min_spare_servers配置数(表示请求比较多,worker处理不过来了)则会fork worker进程,但总的worker数不能超过pm.max_children,如果master发现空闲worker数超过了pm.max_spare_servers(表示闲着的worker太多了)则会杀掉一些worker,避免占用过多资源,master通过这4个值来控制worker数
ondemand: 这种方式一般很少用,在启动时不分配worker进程,等到有请求了后再通知master进程fork worker进程,总的worker数不超过pm.max_children,处理完成后worker进程不会立即退出,当空闲时间超过pm.process_idle_timeout后再退出
前面介绍到在fpm_run()master进程将进入fpm_event_loop():

void fpm_event_loop(int err)
{
//创建一个io read的监听事件,这里监听的就是在fpm_init()阶段中通过socketpair()创建管道sp[0]
//当sp[0]可读时将回调fpm_got_signal()
fpm_event_set(&signal_fd_event, fpm_signals_get_fd(), FPM_EV_READ, &fpm_got_signal, NULL);
fpm_event_add(&signal_fd_event, 0);
//如果在php-fpm.conf配置了request_terminate_timeout则启动心跳检查
if (fpm_globals.heartbeat > 0) {
    fpm_pctl_heartbeat(NULL, 0, NULL);
}
//定时触发进程管理
fpm_pctl_perform_idle_server_maintenance_heartbeat(NULL, 0, NULL);

//进入事件循环,master进程将阻塞在此
while (1) {
    ...
    //等待IO事件
    ret = module->wait(fpm_event_queue_fd, timeout);
    ...
    //检查定时器事件
    ...
}
}
登入後複製

这就是master整体的处理,其进程管理主要依赖注册的几个事件,接下来我们详细分析下这几个事件的功能。

(1)sp[1]管道可读事件:

在fpm_init()阶段master曾创建了一个全双工的管道:sp,然后在这里创建了一个sp[0]可读的事件,当sp[0]可读时将交由fpm_got_signal()处理,向sp[1]写数据时sp[0]才会可读,那么什么时机会向sp[1]写数据呢?前面已经提到了:当master收到注册的那几种信号时会写入sp[1]端,这个时候将触发sp[0]可读事件。

这个事件是master用于处理信号的,我们根据master注册的信号逐个看下不同用途:

SIGINT/SIGTERM/SIGQUIT: 退出fpm,在master收到退出信号后将向所有的worker进程发送退出信号,然后master退出
SIGUSR1: 重新加载日志文件,生产环境中通常会对日志进行切割,切割后会生成一个新的日志文件,如果fpm不重新加载将无法继续写入日志,这个时候就需要向master发送一个USR1的信号
SIGUSR2: 重启fpm,首先master也是会向所有的worker进程发送退出信号,然后master会调用execvp()重新启动fpm,最后旧的master退出
SIGCHLD: 这个信号是子进程退出时操作系统发送给父进程的,子进程退出时,内核将子进程置为僵尸状态,这个进程称为僵尸进程,它只保留最小的一些内核数据结构,以便父进程查询子进程的退出状态,只有当父进程调用wait或者waitpid函数查询子进程退出状态后子进程才告终止,fpm中当worker进程因为异常原因(比如coredump了)退出而非master主动杀掉时master将受到此信号,这个时候父进程将调用waitpid()查下子进程的退出,然后检查下是不是需要重新fork新的worker
具体处理逻辑在fpm_got_signal()函数中,这里不再罗列。

(2)fpm_pctl_perform_idle_server_maintenance_heartbeat():

这是进程管理实现的主要事件,master启动了一个定时器,每隔1s触发一次,主要用于dynamic、ondemand模式下的worker管理,master会定时检查各worker pool的worker进程数,通过此定时器实现worker数量的控制,处理逻辑如下:

static void fpm_pctl_perform_idle_server_maintenance(struct timeval now)
{
for (wp = fpm_worker_all_pools; wp; wp = wp->next) {
struct fpm_child_s last_idle_child = NULL; //空闲时间最久的worker
int idle = 0; //空闲worker数
int active = 0; //忙碌worker数
    for (child = wp->children; child; child = child->next) {
        //根据worker进程的fpm_scoreboard_proc_s->request_stage判断
        if (fpm_request_is_idle(child)) {
            //找空闲时间最久的worker
            ...
            idle++;
        }else{
            active++;
        }
    }
    ...
    //ondemand模式
    if (wp->config->pm == PM_STYLE_ONDEMAND) {
        if (!last_idle_child) continue;

        fpm_request_last_activity(last_idle_child, &last);
        fpm_clock_get(&now);
        if (last.tv_sec < now.tv_sec - wp->config->pm_process_idle_timeout) {
            //如果空闲时间最长的worker空闲时间超过了process_idle_timeout则杀掉该worker
            last_idle_child->idle_kill = 1;
            fpm_pctl_kill(last_idle_child->pid, FPM_PCTL_QUIT);
        } 
        continue;
    }
    //dynamic
    if (wp->config->pm != PM_STYLE_DYNAMIC) continue;
    if (idle > wp->config->pm_max_spare_servers && last_idle_child) {
        //空闲worker太多了,杀掉
        last_idle_child->idle_kill = 1;
        fpm_pctl_kill(last_idle_child->pid, FPM_PCTL_QUIT);
        wp->idle_spawn_rate = 1;
        continue;
    }
    if (idle < wp->config->pm_min_spare_servers) {
        //空闲worker太少了,如果总worker数未达到max数则fork
        ...
    }
}
}
登入後複製

(3)fpm_pctl_heartbeat():

这个事件是用于限制worker处理单个请求最大耗时的,php-fpm.conf中有一个request_terminate_timeout的配置项,如果worker处理一个请求的总时长超过了这个值那么master将会向此worker进程发送kill -TERM信号杀掉worker进程,此配置单位为秒,默认值为0表示关闭此机制,另外fpm打印的slow log也是在这里完成的。

static void fpm_pctl_check_request_timeout(struct timeval now)
{
struct fpm_worker_pool_s wp;
for (wp = fpm_worker_all_pools; wp; wp = wp->next) {
    int terminate_timeout = wp->config->request_terminate_timeout;
    int slowlog_timeout = wp->config->request_slowlog_timeout;
    struct fpm_child_s *child;

    if (terminate_timeout || slowlog_timeout) { 
        for (child = wp->children; child; child = child->next) {
            //检查当前当前worker处理的请求是否超时
            fpm_request_check_timed_out(child, now, terminate_timeout, slowlog_timeout);
        }
    }
}
}
登入後複製

除了上面这几个事件外还有一个没有提到,那就是ondemand模式下master监听的新请求到达的事件,因为ondemand模式下fpm启动时是不会预创建worker的,有请求时才会生成子进程,所以请求到达时需要通知master进程,这个事件是在fpm_children_create_initial()时注册的,事件处理函数为fpm_pctl_on_socket_accept(),具体逻辑这里不再展开,比较容易理解。

到目前为止我们已经把fpm的核心实现介绍完了,事实上fpm的实现还是比较简单的。

以上是Fpm啟動機制及流程的詳細分析(附程式碼)的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

解決Ubuntu缺少PHP-FPM的問題 解決Ubuntu缺少PHP-FPM的問題 Mar 08, 2024 pm 09:45 PM

解決Ubuntu缺少PHP-FPM的問題,需要具體程式碼範例在Ubuntu系統中,安裝和設定PHP-FPM是常見的操作,但有時候在安裝過程中會出現缺少PHP-FPM的問題。本文將詳細介紹如何解決這個問題,並提供具體的程式碼範例來幫助您完成安裝和設定。 1.檢查PHP-FPM是否已安裝首先,使用下列指令檢查系統中是否已經安裝了PHP-FPM:dpkg-l|gr

解決Linux環境下PHP7-FPM啟動失敗的方法 解決Linux環境下PHP7-FPM啟動失敗的方法 Mar 11, 2024 pm 02:12 PM

解決Linux環境下PHP7-FPM啟動失敗的方法在使用Linux伺服器建立網站或應用程式時,PHP7-FPM是常用的PHPFastCGI進程管理器。但是有時會遇到PHP7-FPM啟動失敗的問題,這可能會導致網站無法正常存取。在本文中,我們將介紹一些常見的解決方法,並提供具體的程式碼範例。希望這些資訊能幫助你快速解決PHP7-FPM啟動失敗的情況。檢查

如何應對Linux下PHP7-FPM啟動失敗的狀況 如何應對Linux下PHP7-FPM啟動失敗的狀況 Mar 10, 2024 pm 09:54 PM

Linux下的PHP7-FPM是一種常見的PHPFastCGI進程管理器,通常用來處理PHP程式的運作。然而有時在配置或部署過程中,可能會遇到PHP7-FPM啟動失敗的情況,導致網站無法正常存取。在這種情況下,我們需要採取一些措施來應對和解決問題。 1.檢查錯誤日誌首先要查看PHP7-FPM的錯誤日誌,通常錯誤日誌位於/var/log/php7-fpm

nginx平滑重啟和FPM平滑重啟是什麼 nginx平滑重啟和FPM平滑重啟是什麼 May 23, 2023 pm 09:08 PM

平滑重啟GR是GracefulRestart(平滑重啟)的簡稱,是確保在協定重新啟動時轉送業務不會中斷的機制。 GR機制的核心在於:當某設備進行協定重新啟動時,能夠通知其周邊設備在一定時間內將到該設備的鄰居關係和路由保持穩定。在協定重新啟動後,週邊設備協助其進行資訊(包括支援GR的路由/MPLS相關協定所維護的各種拓樸、路由和會話資訊)同步,在盡量短的時間內使該設備恢復到重新啟動前的狀態。在整個協定重新啟動過程中不會產生路由振盪,封包轉送路徑也沒有任何改變,整個系統可以不間斷地轉送資料。這個過程即稱為平滑重啟。

nginx+php-fpm服務HTTP狀態碼502怎麼解決 nginx+php-fpm服務HTTP狀態碼502怎麼解決 May 21, 2023 am 08:07 AM

我們的一個web項目,由於新上城市增多,導致訪問量增大,db壓力增大,作為提供接口的業務方,最近被下游反饋大量請求“502”。 502,badgateway,通常都是upstream(這裡就是php)出錯,對於php,造成502的原因常見的就是腳本執行超過timeout設定時間,或者timeout設定過大,導致php進程長時間不能被釋放,沒有空閒worker進程來接客。我們的專案就是php執行時間設定過短導致的,對於這種情況,可以先適當增大php的執行時間,先保證清除502,優化的事情畢竟要花更多

解決Ubuntu系統中缺少PHP-FPM的挑戰 解決Ubuntu系統中缺少PHP-FPM的挑戰 Mar 08, 2024 pm 02:48 PM

在解決Ubuntu系統中缺少PHP-FPM的挑戰時,需要遵循一系列步驟來安裝和設定PHP-FPM。 PHP-FPM(FastCGIProcessManager)是一個處理動態頁面請求的進程管理器,能夠提高PHP應用程式的效能和穩定性。以下將詳細介紹如何在Ubuntu系統上安裝和設定PHP-FPM,並提供具體的程式碼範例來幫助解決這項挑戰。第一步:更新系統在進

Nginx中使用PHP-FPM時記錄PHP錯誤日誌怎麼配置 Nginx中使用PHP-FPM時記錄PHP錯誤日誌怎麼配置 May 15, 2023 am 08:58 AM

nginx與apache不一樣,在apache中可以直接指定php的錯誤日誌,那樣在php執行中的錯誤訊息就直接輸入到php的錯誤日誌中,可以方便查詢。在nginx事情就變成這樣:nginx只對頁面的存取做access記錄日誌了。不會有php的errorlog資訊。 nginx把對php的請求發給php-fpmfastcgi進程來處理,預設的php-fpm只會輸出php-fpm的錯誤訊息,在php-fpm的errorslog裡也看不到php的errorlog。原因是php-fpm的設定檔php

Linux伺服器上PHP7-FPM啟動異常的處理技巧 Linux伺服器上PHP7-FPM啟動異常的處理技巧 Mar 11, 2024 am 11:09 AM

在Linux伺服器上使用PHP7-FPM作為伺服器端腳本運行環境是常見的做法,但有時候可能會遇到啟動異常的情況。本文將介紹一些處理PHP7-FPM啟動異常的技巧,同時提供一些具體的程式碼範例,幫助讀者更好地解決問題。 1.檢查設定檔PHP7-FPM的設定檔通常位於/etc/php/7.x/fpm/pool.d/目錄下,確保設定檔中各項配置正確無誤。可以使

See all articles