• Linux -- PHP-FPM的源码解析和模型


    1.进程管理模式

    PHP-FPM由1个master进程和N个worker进程组成。其中,Worker进程由master进程fork而来。

    PHP-FPM有3种worker进程管理模式。

    1. Static

    初始化时调用fpm_children_make(wp,0,0,1)函数fork出pm.max_children数量的worker进程,后续不再动态增减worker进程数量

    2. Dynamic

    初始化时调用fpm_children_make(wp,0,0,1)函数fork出pm.start_servers数量的worker进程,然后由每隔1秒触发的心跳事件fpm_pctl_perform_idle_server_maintenance()来维护空闲woker进程数量:空闲worker进程数量若多于pm.max_spare_servers则kill进程,若少于pm.min_spare_servers则fork进程。

    3. Ondemand

    初始化时不生成worker进程,但注册事件ondemand_event监听listening_socket。当listen_socket收到request,先检查是否存在已生成的空闲的worker进程,若存在就使用这个空闲进程,否则fork一个新的进程。每隔1秒触发的心跳事件fpm_pctl_perform_idle_server_maintenance()会kill掉空闲时间超过pm.process_idle_timeout的worker进程

    2. 标准IO

    FastCGI的典型流程如下:

    (1)     web server(例如nginx或apache)接受到一个请求。然后,web server通过unix域socket或TCP socket连接到FastCGI应用。

    (2)     FastCGI应用可以选择接受或拒绝这个连接。如果接受了连接,FastCGI应用会试图从stream中读取到一个packet

    (3)     Web server发送的第一个packet是BEGIN_REQUEST packet。BEGIN_REQUEST packet包含一个独一无二的request ID。所有该request的后续packet都被这个ID标记。

    Unix系统中,标准输入的文件描述符是0,标准输出的文件描述符是1,标准错误输出的文件描述符是2,宏定义如下:

    #define STDIN_FILENO  0

    #define STDOUT_FILENO  1

    #define STDERR_FILENO  2

    PHP-FPM重定向了这三个标准IO。

    在master进程中,STDIN_FILENO(0)和STDOUT_FILENO(1)均重定向到”/dev/null”。 STDERR_FILENO(2)重定向到error_log。

    在worker进程中,STDIN_FILENO(0)重定向到listening_socket。如果catch_workers_output为no的话,STDOUT_FILENO(1)和STDERR_FILENO(2)均重定向到”/dev/null”。否则,STDOUT_FILENO(1)重定向到1个pipe的写端,而这个pipe的fd读端保存于master进程child链表对应的child节点结构的fd_stdout元素上。同样的,STDERR_FILENO(2)也重定向到1个pipe的写端,而这个pipe的读端fd保存于master进程child链表对应的child节点结构的fd_stderr元素上。以上两个位于master进程的pipe读端由master进程的reactor进行监听。

    3. 进程间通信模型

    PHP-FPM中的进程间通信主要分为

    1. Master进程和worker进程之间的通信

    前面讲过master进程和worker进程间有两条pipe。Worker进程向STDOUT_FILE或STDERR_FILENO中写信息,master进程收到信息后写入log。Master进程用reactor监听两个pipe

    2. Web server与worker进程之间的通信

    Worker进程阻塞在accept(listening socket)监听web server

    3. Web server与master进程之间的通信

    当pm模式是ondemand时,master进程会在reactor注册listening_socket的监听事件。当有request到来,master进程将生成一个worker进程

    PHP-FPM采用的进程模型是进程池。Worker进程继承由master进程socket(),bind(),listen()的socket fd并直接阻塞在accept()上。当有一个request到来,进程池中的一个worker进程接受request。当这个worker进程完成执行,就会返回进程池等待新的request。这事实上是leader/follower模式。在leader/follower模式中,仅有leader阻塞等待,其他进程都在sleep。同样的,在FPM中,由于linux内核解决了accept()的惊群问题,新request同样只会唤醒一个worker进程。在这里,leader的继任是由linux内核决定的(当然,你也可以用mutex守卫accept代码段来确保leader只有一位)。

    Worker进程处理所有IO和逻辑。Master进程负责worker进程的生成和销毁。Master进程的Reactor注册了三个可读事件和四个定时器事件。当pm是ondemand时,额外注册一个可读事件。三个可读事件分别是1个信号事件,2个pipe事件。

    Fpm_event_s 结构:

    struct fpm_event_s {
    
             int fd;                
    
             struct timeval timeout;  
    
             struct timeval frequency;
    
             void (*callback)(struct fpm_event_s *, short, void *);
    
             void *arg;
    
             int flags;
    
             int index;              
    
             short which;          
    
    };

    Flags代表该事件的类型。FPM中flags的值有三种:

    FPM_EV_READ : 可读事件

    FPM_EV_PERSIST : 心跳事件

    FPM_EV_READ | FPM_EV_EDGE : 边缘触发的可读事件

    Which代表该事件位于哪个事件队列。其值有两种:

    FPM_EV_READ : 位于可读事件队列

    FPM_EV_TIMEOUT : 位于定时器事件队列

    事件队列的结构是双向链表

    typedef struct fpm_event_queue_s {
    
             struct fpm_event_queue_s *prev;
    
             struct fpm_event_queue_s *next;
    
             struct fpm_event_s *ev;
    
    } fpm_event_queue;
    
     
    
    static struct fpm_event_queue_s *fpm_event_queue_timer = NULL;
    
    static struct fpm_event_queue_s *fpm_event_queue_fd = NULL;  

    fpm_event_queue_timer是定时器事件队列,fpm_event_queue_fd是可读事件队列。定时器事件队列并没有采用最小堆,红黑树或事件轮等结构,因为这个队列非常小,没有必要使用这些复杂结构。但是如果把定时器事件队列改为升序链表,对性能应该会有提升。

    Fd和index仅在可读事件中使用。fd表示被监听的文件描述符。Index的值与使用哪个IO复用API有关。在epoll和select中,index的值等于fd的值。在poll中,index是该fd在描述符集fds[]中位置的下标。在心跳事件中,fd == -1,index == -1。

    Struct timeval timeout和struct timeval frequency仅在心跳事件中使用。frequency表示每隔多少时间触发一次心跳事件,Timeout表示下一次触发心跳事件的时刻,通常由now与frequency相加而得。在可读事件中,这两个结构不设置。

    Signal_fd_event事件

    先来看fpm中的信号处理。

    int fpm_signals_init_main() /* {{{ */
    
    {
    
             struct sigaction act;
    
     
    
            /* create socketpair*/
    
             if (0 > socketpair(AF_UNIX, SOCK_STREAM, 0, sp)) {
    
                       zlog(ZLOG_SYSERROR, "failed to init signals: socketpair()");
    
                       return -1;
    
             }
    
     
    
            /*将两个socket设为NONBLOCK*/
    
             if (0 > fd_set_blocked(sp[0], 0) || 0 > fd_set_blocked(sp[1], 0)) {
    
                       zlog(ZLOG_SYSERROR, "failed to init signals: fd_set_blocked()");
    
                       return -1;
    
             }
    
     
    
            /*如果程序成功地运行完毕,则自动关闭这两个fd*/
    
             if (0 > fcntl(sp[0], F_SETFD, FD_CLOEXEC) || 0 > fcntl(sp[1], F_SETFD, FD_CLOEXEC)) {
    
                       zlog(ZLOG_SYSERROR, "falied to init signals: fcntl(F_SETFD, FD_CLOEXEC)");
    
                       return -1;
    
             }
    
     
    
             memset(&act, 0, sizeof(act));
    
            /* 将信号处理函数设为sig_handler*/
    
             act.sa_handler = sig_handler;
    
            /* 将所有信号加入信号集*/
    
             sigfillset(&act.sa_mask);
    
     
    
            /* 更改指定信号的action */
    
             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)) {
    
     
    
                       zlog(ZLOG_SYSERROR, "failed to init signals: sigaction()");
    
                       return -1;
    
             }
    
             return 0;
    
    }

    master进程注册了SIGTERM,SIGINT,SIGUSR1,SIGUSR2,SIGCHLD,SIGQUIT,并创建了socketpair sp[]。当收到这些信号时,master进程将向sp[1]中写一个代表该信号的字符。

                       [SIGTERM] = 'T',

                       [SIGINT]  = 'I',

                       [SIGUSR1] = '1',

                       [SIGUSR2] = '2',

                       [SIGQUIT] = 'Q',

                       [SIGCHLD] = 'C'

    Sp[0]就是Signal_fd_event事件监听的fd。该事件的回调函数对不同的信号(从sp[0]读到的代表信号的字符)做出不同的反应。

    信号SIGCHLD:

    调用fpm_children_bury();该函数调用waitpid()分析子进程的status,根据情况决定是否重启子进程。

    信号SIGINT , SIGTERM:

    调用fpm_pctl(FPM_PCTL_STATE_TERMINATING, FPM_PCTL_ACTION_SET);把fpm的状态改为terminating

    信号SIGQUIT

    调用fpm_pctl(FPM_PCTL_STATE_FINISHING, FPM_PCTL_ACTION_SET); 把fpm的状态改为finishing

    信号SIGUSR1

    调用fpm_stdio_open_error_log(1)重启error log file

    调用fpm_log_open(1)重启access log file 并重启所有子进程

    信号SIGUSR2

    调用fpm_pctl(FPM_PCTL_STATE_RELOADING, FPM_PCTL_ACTION_SET); 把fpm的状态改为reloading

    子进程的信号处理

    子进程关闭了socketpair,并把SIGTERM,SIGINT,SIGUSR1,SIGUSR2,SIGHLD重新设为默认动作,把SIGQUIT设为soft quit。

    int fpm_signals_init_child() /* {{{ */
    
    {
    
             struct sigaction act, act_dfl;
    
     
    
             memset(&act, 0, sizeof(act));
    
             memset(&act_dfl, 0, sizeof(act_dfl));
    
     
    
             act.sa_handler = &sig_soft_quit;
    
             // 当system call或library function阻塞时一个信号到来。系统默认会返回错误并设置errno为EINTR.这里设为自动重启
    
             act.sa_flags |= SA_RESTART;
    
     
    
             act_dfl.sa_handler = SIG_DFL;
    
     
    
             close(sp[0]);
    
             close(sp[1]);
    
     
    
             if (0 > sigaction(SIGTERM,  &act_dfl,  0) ||
    
                 0 > sigaction(SIGINT,   &act_dfl,  0) ||
    
                 0 > sigaction(SIGUSR1,  &act_dfl,  0) ||
    
                 0 > sigaction(SIGUSR2,  &act_dfl,  0) ||
    
                 0 > sigaction(SIGCHLD,  &act_dfl,  0) ||
    
                 0 > sigaction(SIGQUIT,  &act,      0)) {
    
     
    
                       zlog(ZLOG_SYSERROR, "failed to init child signals: sigaction()");
    
                       return -1;
    
             }
    
     
    
             zend_signal_init();
    
             return 0;
    
    }

    其一是可读事件队列。其二是定时器(timer)队列。

  • 相关阅读:
    08-Linux命令【rm】
    07-Linux命令【mv】
    06-Linux命令【cp】
    05-Linux命令【rmdir】
    04-Linux命令【mkdir】
    03-Linux命令【ls】
    02-Linux命令【cd】
    01-Linux命令【pwd】
    智慧城市3D园区
    自我觉醒
  • 原文地址:https://www.cnblogs.com/tuowang/p/9398971.html
Copyright © 2020-2023  润新知