关于php-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协议时采用的是这种模式。
master进程的作用是什么?
fpm是一种多进程的模型,由一个master进程和若干worker进程组成(具体数量需要看php-fpm.conf的配置)master启动的时候回创建一个socket,但是不会接受,处理请求,而是fork出worker子进程去接受和处理请求
所以master进程的主要作用就是管理worker进程,负责fork或者kill掉子进程,当请求多worker处理不过来了这是master会fork新的worker进程处理,如果空闲的进程多的时候,就会kill掉一些worker进程,避免占用浪费系统资源(像一个工头,他不干活,监督大家搬砖,活多了找几个人来干活,活儿少了,空闲的工人多了就开除几个)
worker进程的作用是什么?
worker进程的主要工作是处理请求,每个worker进程都会**竞争地**accept请求,接受成功后会解析fastcgi,然后执行脚本,完成后关闭请求,继续等待新的连接,这是worker进程的声明周期。
概括来说,fpm的实现就是创建一个master进程,在master进程中创建并监听socket,然后fork出多个子进程,这些子进程各自accept请求,子进程的处理非常简单,它在启动后阻塞在accept上,有请求到达后开始读取请求数据,读取完成后开始处理然后再返回,在这期间是不会接收其它请求的,也就是说fpm的子进程同时只能响应一个请求,只有把这个请求处理完成后才会accept下一个请求,这一点与nginx的事件驱动有很大的区别,nginx的子进程通过epoll管理套接字,如果一个请求数据还未发送完成则会处理下一个请求,即一个进程会同时连接多个请求,它是非阻塞的模型,只处理活跃的套接字。
进程之间的通信有哪些呢? 1 管道,2命名管道,3队列,4信号量 5共享内存
fpm的master进程与worker进程之间不会直接进行通信,master通过共享内存获取worker进程的信息,比如worker进程当前状态、已处理请求数等,当master进程要杀掉一个worker进程时则通过发送信号的方式通知worker进程。
fpm可以同时监听多个端口,每个端口对应一个worker pool,而每个pool下对应多个worker进程,类似nginx中server概念,这些归属不同的pool的worker进程任由一个master管理。(是不是说fpm只能有一个master)
等不及了,上源码吧
worker pool的结构为 fpm_worker_pool_s,pool之间构成一个链表
//代码文件 php/sapi/fpm/fpm/fpm_worker_pool.h
struct fpm_worker_pool_s {
struct fpm_worker_pool_s *next; //指向下一个fpm_worker_pool_s的指针
struct fpm_worker_pool_config_s *config; //指向fpm配置信息
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; //当前pool的worker链表,每个worker链表对应一个fpm_child_s 结构
int running_children; //当前pool的worker总数
int idle_spawn_rate;
int warn_max_children;
#if 0
int warn_lq;
#endif
struct fpm_scoreboard_s *scoreboard; //记录worker的运行信息,空闲忙碌的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
};
这个是当php-fpm.conf解析后,会解析到fpm_worker_pool_config_s。
接下来看下php-fpm的启动过程
//sapi/fpm/fpm/fpm_main.c
int main(int argc, char *argv[])
{
...
//注册SAPI:将全局变量sapi_module设置为cgi_sapi_module
sapi_startup(&cgi_sapi_module);
...
//执行php_module_starup() 第一个阶段
if (cgi_sapi_module.startup(&cgi_sapi_module) == FAILURE) {
return FPM_EXIT_SOFTWARE;
}
...
//初始化
if(0 > fpm_init(...)){
...
}
...
fpm_is_running = 1;
fcgi_fd = fpm_run(&max_requests);//后面都是worker进程的操作,master进程不会走到下面
parent = 0;
...
/* library is already initialized, now init our request */
request = fpm_init_request(fcgi_fd);
zend_first_try {
//worker进程阻塞,等待请求
while (EXPECTED(fcgi_accept_request(request) >= 0)) {
...
fpm_request_executing(); //初始化计分板的信息
php_execute_script(&file_handle); //执行脚本
...
fastcgi_request_done:
php_request_shutdown((void *) 0);//关闭请求
...
requests++;
//当请求数达到php-fpm所设定的pm.max_requests数值时候是如下操作
if (UNEXPECTED(max_requests && (requests == max_requests))) {
fcgi_request_set_keep(request, 0);
fcgi_finish_request(request, 0);
break;
}
/* end of fastcgi loop */
}
fcgi_destroy_request(request);
fcgi_shutdown();
}
}
里面涉及到了 两个主要的过程 php_init 和 php_run
php_init 这里干了什么 主要是进行了一些初始化操作
//sapi/fpm/fpm/fpm.c
int fpm_init(int argc, char **argv, char *config, char *prefix, char *pid, int test_conf, int run_as_root, int force_daemon, int force_stderr) /* {{{ */
{
fpm_globals.argc = argc;
fpm_globals.argv = argv;
if (config && *config) {
fpm_globals.config = strdup(config);
}
fpm_globals.prefix = prefix;
fpm_globals.pid = pid;
fpm_globals.run_as_root = run_as_root;
fpm_globals.force_stderr = force_stderr;
if (0 > fpm_php_init_main() ||
0 > fpm_stdio_init_main() ||
0 > fpm_conf_init_main(test_conf, force_daemon) //解析PHP-fpm配置文件 ||
0 > fpm_unix_init_main() 启动进程创建一个管道pipe 用于和子进程(master进程)通信,子进程完成初始化后,会通过这个管道给启动进程发消息, ||
0 > fpm_scoreboard_init_main() 分配用于记录worker进程运行信息的结构,次结构分配在共享内存上,
||
0 > fpm_pctl_init_main() ||
0 > fpm_env_init_main() ||
0 > fpm_signals_init_main() //创建管道,注册信号量SIGTERM、SIGINT、SIGUSR1、SIGUSR2、SIGCHLD、SIGQUIT这些信号时将调用sig_handler()处理||
0 > fpm_children_init_main() ||
0 > fpm_sockets_init_main() //创建每个worker pool的socket套接字,启动后讲监听次socket接受请求||
0 > fpm_worker_pool_init_main() //master的事件管理,用于管理IO,定时事件||
0 > fpm_event_init_main()) {
if (fpm_globals.test_successful) {
exit(FPM_EXIT_OK);
} else {
zlog(ZLOG_ERROR, "FPM initialization failed");
return -1;
}
}
if (0 > fpm_conf_write_pid()) {
zlog(ZLOG_ERROR, "FPM initialization failed");
return -1;
}
fpm_stdio_init_final();
zlog(ZLOG_NOTICE, "fpm is running, pid %d", (int) fpm_globals.parent_pid);
return 0;
}
(1)fpm_conf_init_main():
解析php-fpm.conf配置文件,分配worker pool内存结构并保存到全局变量中:fpm_worker_pool_config_s *config,各worker pool配置解析到fpm_worker_pool_s->config中。看下fpm_worker_pool_config_s结构体里面的属性是不是很熟悉和
php-fpm.conf一样
PS关于fpm_worker_pool_config_s里面只是进程池的属性,全局的属性在fpm_global_config_s 结构体中
//sapi/fpm/fpm/fpm_conf.c
/*
* Please keep the same order as in fpm_conf.c and in php-fpm.conf.in
*/
struct fpm_worker_pool_config_s {
char *name;
char *prefix;
char *user;
char *group;
char *listen_address;
int listen_backlog;
/* Using chown */
char *listen_owner;
char *listen_group;
char *listen_mode;
char *listen_allowed_clients;
int process_priority;
int pm;
int pm_max_children;
int pm_start_servers;
int pm_min_spare_servers;
int pm_max_spare_servers;
int pm_process_idle_timeout;
int pm_max_requests;
char *pm_status_path;
char *ping_path;
char *ping_response;
char *access_log;
char *access_format;
char *slowlog;
int request_slowlog_timeout;
int request_slowlog_trace_depth;
int request_terminate_timeout;
int rlimit_files;
int rlimit_core;
char *chroot;
char *chdir;
int catch_workers_output;
int clear_env;
char *security_limit_extensions;
struct key_value_s *env;
struct key_value_s *php_admin_values;
struct key_value_s *php_values;
#ifdef HAVE_APPARMOR
char *apparmor_hat;
#endif
#ifdef HAVE_FPM_ACL
/* Using Posix ACL */
char *listen_acl_users;
char *listen_acl_groups;
#endif
};