PHP-FPM 生命周期
PHP-FPM是一种多进程模型,主要由Master进程以及Worker进程组成,所有的cgi请求都会交由Worker进程处理。Master进程主要维护worker进程。
而worker进程的工作方式是抢占/竞争的方式,当一个accept请求过来的时候,谁先拿到算谁的,拿到后转化为FastCGIRquest,交由脚本处理。
整个模型如下图:
worker pool
每个进程池负责监听一个端口,多个进程池之间用链表关联。在其中也定义了children,fpm_child_s是一个双向链表,其中包含了子进程的一些独有信息,包括自己的PID,自己所属的进程池,fpm事件,标准输出等等。 传送门:src-path/sapi/fpm/fpm/fpm_worker_pool.h
/*abel: FPM 进程池
* link list实现*/
struct fpm_worker_pool_s {
struct fpm_worker_pool_s *next; //Next worker
struct fpm_worker_pool_config_s *config; //php-fpm.conf
char *user, *home; /* for setting env USER and HOME */
enum fpm_address_domain listen_address_domain;
int listening_socket; //套接字
int set_uid, set_gid; /* config uid and gid */
int socket_uid, socket_gid, socket_mode;
/* runtime */
struct fpm_child_s *children; //进程池中的全部子进程 doubly link list @ fpm_children.h line 20
int running_children;
int idle_spawn_rate;
int warn_max_children;
#if 0
int warn_lq;
#endif
struct fpm_scoreboard_s *scoreboard; //worker pool 中worker的运行信息
int log_fd;
char **limit_extensions;
/* for ondemand PM */
struct fpm_event_s *ondemand_event;
int socket_event_set;
#ifdef HAVE_FPM_ACL
void *socket_acl;
#endif
};
src-path/sapi/fpm/fpm/fpm_children.h
/*abel: fpm child结构 doubly link list
* 包含属于哪个worker_pool,自身pid,等信息*/
struct fpm_child_s {
struct fpm_child_s *prev, *next;
struct timeval started;
struct fpm_worker_pool_s *wp;
struct fpm_event_s ev_stdout, ev_stderr;
int shm_slot_i;
int fd_stdout, fd_stderr;
void (*tracer)(struct fpm_child_s *);
struct timeval slow_logged;
int idle_kill;
pid_t pid;
int scoreboard_i;
struct zlog_stream *log_stream;
};
启动
FPM启动函数在fpm_main.c中。源码比较长,其中比较重要的几个环节如下:
......
//sapi startup @ line:1580
php_printf("startup sapi_startup(&cgi_sapi_module)\n");
sapi_startup(&cgi_sapi_module);
......
//sapi_module startup @ line:1770
if (cgi_sapi_module.startup(&cgi_sapi_module) == FAILURE) {
#ifdef ZTS
tsrm_shutdown();
#endif
return FPM_EXIT_SOFTWARE;
}
......
//fpm_init
/*abel:FPM 初始化,处理php-fpm.conf*/
ret = fpm_init(argc, argv, fpm_config ? fpm_config : CGIG(fpm_config), fpm_prefix, fpm_pid, test_conf, php_allow_to_run_as_root, force_daemon, force_stderr);
......
//FPM RUN
fcgi_fd = fpm_run(&max_requests);
//初始化fpm_request对象
request = fpm_init_request(fcgi_fd);
/*abel:
* zend engine 自行实现的try cache*/
zend_first_try {
/*abel:
* 进入抢占模型
* fcgi_accept_request 从Cgi中获得请求,一旦获得请求后,直接进行处理*/
while (EXPECTED(fcgi_accept_request(request) >= 0)) {
......
/*request startup*/
if (UNEXPECTED(php_request_startup() == FAILURE)) {
fcgi_finish_request(request, 1);
SG(server_context) = NULL;
php_module_shutdown();
return FPM_EXIT_SOFTWARE;
}
......
/*abel:执行PHP脚本*/
php_execute_script(&file_handle);
/*abel:cgi请求结束*/
fastcgi_request_done:
......
fpm_request_end();
fpm_log_write(NULL);
......
fpm_request_executing();
php_request_shutdown((void *) 0);
......
}//end while
fcgi_destroy_request(request);
fcgi_shutdown();
......
} zend_catch {
exit_status = FPM_EXIT_SOFTWARE;
} zend_end_try();
out:
SG(server_context) = NULL;
php_module_shutdown();
if (parent) {
sapi_shutdown();
}
从源码中大致可以看出PHPFPM的生命周期如下
fpm_run函数是整个main函数的核心,他负责通过复制自己创造子进程。创造的子进程数量符合php-fpm.conf中的设定的时候,主进程将进入一个fpm_event_loop的无限循环状态,监控并管理子进程的“生死”。
//FPM RUN
fcgi_fd = fpm_run(&max_requests);
所以以上代码的后续逻辑均是由子进程完成的。 来看一下php_run代码:
/*abel:
* children: return listening socket
* parent: never return
* abel:子进程返回监听的socket
* 父进程永远不返回 */
int fpm_run(int *max_requests)
{
struct fpm_worker_pool_s *wp;
/* create initial children in all pools */
for (wp = fpm_worker_all_pools; wp; wp = wp->next) {
int is_parent;
/*abel:
* return 0:当前进程为子进程
* return 1:当前进程为父进程且fork子进程成功
* return 2:当前进程为父进程但fork失败*/
is_parent = fpm_children_create_initial(wp);
if (!is_parent) {
goto run_child;
}
/* handle error */
if (is_parent == 2) {
fpm_pctl(FPM_PCTL_STATE_TERMINATING, FPM_PCTL_ACTION_SET);
fpm_event_loop(1);
}
}
/* run event loop forever */
/*abel:
* 主进程陷入死循环*/
fpm_event_loop(0);
/*abel:
* 子进程需要做的事情
* 1、初始化自身
* 2、继承配置文件中的max_requests
* 3、返回socket */
run_child: /* only workers reach this point */
fpm_cleanups_run(FPM_CLEANUP_CHILD);
*max_requests = fpm_globals.max_requests;
return fpm_globals.listening_socket;
}
当程序进入到fpm_run阶段的时候,会根据不同的进程池fork出不同的子进程,方法fpm_children_create_initial为真正初始化子进程的方法,该方法主要返回三种状态:
- 0:当前进程为子进程,直接进入run_child流程。
- 1:当前进程为父进程且fork子进程成功,继续初始化下一个进程池。
- 2:当前进程为父进程但fork失败,进程进入费撑场监听。
在return1的状态下,表明fork子进程成功,而创造子进程则主要靠fpm_children_make方法:
/*abel:根据进程池配置创建fpm子进程(根据Dynamic/ondemand/static关键字)
* return 0 fork成功 (子进程)
* 1 fork成功 (父进程)
* 2 fork失败(父进程)
* 在父进程中会得到1或者2结果
* 1:
* 1.1、父进程会将生成的child(fpm_child_s)对象的真实pid填充
* 1.2、将该child放进自己的child对象池中
* 在子进程中会得到0*/
int fpm_children_make(struct fpm_worker_pool_s *wp, int in_event_loop, int nb_to_spawn, int is_debug)
{
pid_t pid;
struct fpm_child_s *child;
int max;
static int warned = 0;
if (wp->config->pm == PM_STYLE_DYNAMIC) {
if (!in_event_loop) { /* starting */
max = wp->config->pm_start_servers;
} else {
max = wp->running_children + nb_to_spawn;
}
} else if (wp->config->pm == PM_STYLE_ONDEMAND) {
if (!in_event_loop) { /* starting */
max = 0; /* do not create any child at startup */
} else {
max = wp->running_children + nb_to_spawn;
}
} else { /* PM_STYLE_STATIC */
max = wp->config->pm_max_children;
}
/*
* fork children while:
* - fpm_pctl_can_spawn_children : FPM is running in a NORMAL state (aka not restart, stop or reload)
* - wp->running_children < max : there is less than the max process for the current pool
* - (fpm_global_config.process_max < 1 || fpm_globals.running_children < fpm_global_config.process_max):
* if fpm_global_config.process_max is set, FPM has not fork this number of processes (globaly)
*/
while (fpm_pctl_can_spawn_children() && wp->running_children < max && (fpm_global_config.process_max < 1 || fpm_globals.running_children < fpm_global_config.process_max)) {
warned = 0;
/*abel:准备好一个child(fpm_child_s:doubly link list)结构*/
child = fpm_resources_prepare(wp);
/*abel:不具备条件直接return错误*/
if (!child) {
return 2;
}
/*abel: self clone*/
pid = fork();
switch (pid) {
/*abel:fork success
* 标记自己为子进程 并return成功*/
case 0 :
fpm_child_resources_use(child);
fpm_globals.is_child = 1;
fpm_child_init(wp);
return 0;
/*abel:fork failed
* */
case -1 :
zlog(ZLOG_SYSERROR, "fork() failed");
fpm_resources_discard(child);
return 2;
default :
child->pid = pid;
/*abel:
* tv->tv_sec = ts.tv_sec;
* tv->tv_usec = ts.tv_nsec / 1000;*/
fpm_clock_get(&child->started);
/*??abel:
* 将child装在进父进程监控中
* fpm_stdio_parent_use_pipes(child);
* fpm_child_link(child);*/
fpm_parent_resources_use(child);
zlog(is_debug ? ZLOG_DEBUG : ZLOG_NOTICE, "[pool %s] child %d started", wp->config->name, (int) pid);
}
}
if (!warned && fpm_global_config.process_max > 0 && fpm_globals.running_children >= fpm_global_config.process_max) {
if (wp->running_children < max) {
warned = 1;
zlog(ZLOG_WARNING, "The maximum number of processes has been reached. Please review your configuration and consider raising 'process.max'");
}
}
return 1; /* we are done */
}
再循环完所有的进程池对象链之后,主进程直接进入fpm_event_loop,所以如果是当前是主进程的时候,整个进程会阻塞在fpm_run这一步。之后的处理请求逻辑全部在子进程中完成。
处理请求
子进程会进入到一个阻塞的等待请求的状态中:
while (EXPECTED(fcgi_accept_request(request) >= 0)) {
}
每当请求到来的时候,将会按照如下步骤处理请求:
- php_request_startup 准备好SAPI的各种资源
- php_execute_script 执行PHP脚本
- php_request_shutdown 回收资源,并输出内容
填补一下上面的生命周期:
请求自身的生命周期
在获取到请求后,请求本身会转化成一个fpm_request,其本身也会存在几种状态:
void fpm_request_accepting(); /* hanging in accept() */
void fpm_request_reading_headers(); /* start reading fastcgi request from very first byte */
void fpm_request_info(); /* not a stage really but a point in the php code, where all request params have become known to sapi */
void fpm_request_executing(); /* the script is executing */
void fpm_request_end(void); /* request ended: script response have been sent to web server */
void fpm_request_finished(); /* request processed: cleaning current request */
注释已经解释的很明白了,除了fpm_request_info这个函数外,其他都是fpm_request的各个阶段,也就是fpm request的生命周期:
- request accepting 等待请求阶段
- request reading headers 读取头信息阶段
- request executing 执行阶段
- request end 请求结束阶段:发送内容到web容器
- request finished 请求完成,清理request
在初始化fpm_request时,会将其中的三个执行阶段hook到对象中:
- accepting
- reading headers
- finished
request = fpm_init_request(fcgi_fd);
static fcgi_request *fpm_init_request(int listen_fd) {
fcgi_request *req = fcgi_init_request(listen_fd,
fpm_request_accepting,
fpm_request_reading_headers,
fpm_request_finished);
return req;
}
所以,一个fastCGI请求的生命周期如下: