• 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)队列。

  • 相关阅读:
    Security and Cryptography in Python
    Security and Cryptography in Python
    Security and Cryptography in Python
    Security and Cryptography in Python
    Security and Cryptography in Python
    Security and Cryptography in Python
    Security and Cryptography in Python
    微信小程序TodoList
    C语言88案例-找出数列中的最大值和最小值
    C语言88案例-使用指针的指针输出字符串
  • 原文地址:https://www.cnblogs.com/tuowang/p/9398971.html
Copyright © 2020-2023  润新知