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 ( > socketpair(AF_UNIX, SOCK_STREAM, , sp)) {

                   zlog(ZLOG_SYSERROR, "failed to init signals: socketpair()");

                   return -;

         }

        /*将两个socket设为NONBLOCK*/

         if ( > fd_set_blocked(sp[], ) ||  > fd_set_blocked(sp[], )) {

                   zlog(ZLOG_SYSERROR, "failed to init signals: fd_set_blocked()");

                   return -;

         }

        /*如果程序成功地运行完毕,则自动关闭这两个fd*/

         if ( > fcntl(sp[], F_SETFD, FD_CLOEXEC) ||  > fcntl(sp[], F_SETFD, FD_CLOEXEC)) {

                   zlog(ZLOG_SYSERROR, "falied to init signals: fcntl(F_SETFD, FD_CLOEXEC)");

                   return -;

         }

         memset(&act, , sizeof(act));

        /* 将信号处理函数设为sig_handler*/

         act.sa_handler = sig_handler;

        /* 将所有信号加入信号集*/

         sigfillset(&act.sa_mask);

        /* 更改指定信号的action */

         if ( > sigaction(SIGTERM,  &act, ) ||

              > sigaction(SIGINT,   &act, ) ||

              > sigaction(SIGUSR1,  &act, ) ||

              > sigaction(SIGUSR2,  &act, ) ||

              > sigaction(SIGCHLD,  &act, ) ||

              > sigaction(SIGQUIT,  &act, )) {

                   zlog(ZLOG_SYSERROR, "failed to init signals: sigaction()");

                   return -;

         }

         return ;

}

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, , sizeof(act));

         memset(&act_dfl, , 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[]);

         close(sp[]);

         if ( > sigaction(SIGTERM,  &act_dfl,  ) ||

              > sigaction(SIGINT,   &act_dfl,  ) ||

              > sigaction(SIGUSR1,  &act_dfl,  ) ||

              > sigaction(SIGUSR2,  &act_dfl,  ) ||

              > sigaction(SIGCHLD,  &act_dfl,  ) ||

              > sigaction(SIGQUIT,  &act,      )) {

                   zlog(ZLOG_SYSERROR, "failed to init child signals: sigaction()");

                   return -;

         }

         zend_signal_init();

         return ;

}

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

Linux -- PHP-FPM的源码解析和模型的更多相关文章

  1. linux线程池thrmgr源码解析

    linux线程池thrmgr源码解析 1         thrmgr线程池的作用 thrmgr线程池的作用是提高程序的并发处理能力,在多CPU的服务器上运行程序,可以并发执行多个任务. 2      ...

  2. 源码解析-Volley(转自codeKK)

    Volley 源码解析 本文为 Android 开源项目源码解析 中 Volley 部分项目地址:Volley,分析的版本:35ce778,Demo 地址:Volley Demo分析者:grumoon ...

  3. QT源码解析(一) QT创建窗口程序、消息循环和WinMain函数

    QT源码解析(一) QT创建窗口程序.消息循环和WinMain函数 分类: QT2009-10-28 13:33 17695人阅读 评论(13) 收藏 举报 qtapplicationwindowse ...

  4. quartz源码解析(一)

    本文的起因源于一次quartz的异常,在win2003正常运行的程序放在linux环境就抛出异常了,虽然找出异常没花我多长时间,不过由此加深了对quzrtz的了解:古人说,三折肱,为良医,说明经验对于 ...

  5. Volley 源码解析

    Volley 源码解析 1. 功能介绍 1.1. Volley Volley 是 Google 推出的 Android 异步网络请求框架和图片加载框架.在 Google I/O 2013 大会上发布. ...

  6. 2015.07.20MapReducer源码解析(笔记)

    MapReducer源码解析(笔记)   第一步,读取数据源,将每一行内容解析成一个个键值对,每个键值对供map函数定义一次,数据源由FileInputFormat:指定的,程序就能从地址读取记录,读 ...

  7. 设置ZooKeeper服务器地址列表源码解析及扩展

    设置ZooKeeper服务器地址列表源码解析及扩展 ZooKeeper zooKeeper = new ZooKeeper("192.168.109.130:2181",SESSI ...

  8. IPerf——网络测试工具介绍与源码解析(2)

    对于IPerf源码解析,我是基于2.0.5版本在Windows下执行的情况进行分析的,提倡开始先通过对源码的简单修改使其能够在本地编译器运行起来,这样可以打印输出一些中间信息,对于理解源码的逻辑,程序 ...

  9. IPerf——网络测试工具介绍与源码解析(1)

    IPerf是一个开源的测试网络宽带并能统计并报告延迟抖动.数据包丢失率信息的控制台命令程序,通过参数选项可以方便地看出,通过设置不同的选项值对网络带宽的影响,对于学习网络编程还是有一定的借鉴意义,至少 ...

随机推荐

  1. <<回想>>

    算是一个简单的回忆录,文笔很差,愧对语文老师 突然发现上一篇回忆录,没错就是那个流水账,是去年今天写的...   这是2019年7月的一天,NOI2019刚刚落下帷幕,而小F,则百无聊赖地在高铁站等车 ...

  2. Mybatis面向接口式编程

    Mybatis面向接口编程 1.xml文件书写格式 <?xml version="1.0" encoding="UTF-8" ?> <!DOC ...

  3. BZOJ 2277 strongbox (gcd)

    题意 有一个密码箱,0到n-1中的某些整数是它的密码. 且满足,如果a和b都是它的密码,那么(a+b)%n也是它的密码(a,b可以相等) 某人试了k次密码,前k-1次都失败了,最后一次成功了. 问:该 ...

  4. h5css样式

    兼容性前缀: 谷歌:webkit 火狐:moz ie:ms 欧鹏:o选择器: 属性选择器: * = 包含 {href * = 'www'} ^ = 以什么开头 $ = 以什么结尾 伪类选择器: 第一个 ...

  5. 007_项目制作拍摄视频篇之_《基于ARM与ZigBee的实验室签到系统》

    研究的背景和意义: 随着社会生活节奏的加快,科技日新月异,信息更新迅速,人们之间的交流也变得越来越频繁,社会群体乃至政府之间的交流也朝着轻松.快速.容易管理和控制的方向发展,这种信息交流方式已经逐步得 ...

  6. 转,sql server update set from inner 批量修改的使用

    SQL update select结合语句详解及应用   QL update select语句 最常用的update语法是: 1 2 UPDATE TABLE_NAME SET column_name ...

  7. NetworkX系列教程(4)-设置graph的信息

    小书匠Graph图论 要画出美观的graph,需要对graph里面的节点,边,节点的布局都要进行设置,具体可以看官方文档:Adding attributes to graphs, nodes, and ...

  8. HTTP状态码和支持的方法

    1. HTTP常用状态码   200 ok 客户端请求成功 400 bad request 客户端请求有语法错误,不能被服务器所理解 401 unauthorized 请求要求身份验证,对于登录后请求 ...

  9. [bzoj 4872][六省联考2017]分手是祝愿

    传送门 Description N个灯按照1~N标号,按下一个开关i,所有标号是i的约数的开关都改变状态,目标是关掉所有的灯,如果当前最优策略≤k就直接按照最优策略走.否则随机按下一个开关.给出每个灯 ...

  10. 【知识点】Java常用类库

    1.字符串 修改字符串内容用StringBuffer,没有“+”,需要用append(),否则用String 2.JVM 相关 Runtime,单例模式,通过getRuntime()获取实例,可以调用 ...