其实很早之前就有想过阅读PHP源码,主要还是自己比较懒散,意志不够坚定,一直无法静下心。最近不是很忙,趁着“短暂”的闲暇之余,翻看尘封已久的“PHP源码”文件。
我们知道web服务器与PHP应用之间通过SAPI接口进行交互数据。PHP提供了多种SAPI接口,例如 apache2hander、fastcgi、cli等等。当然,php-fpm也是其中一种。相比其他接口,php-fpm运用更加广泛。
php-fpm是一种master(主)/worker(子)多进程架构,与nginx设计风格有点类似。master进程主要负责CGI及PHP环境初始化、事件监听、子进程状态等等,worker进程负责处理php请求。
在介绍运行原理之前,我们先了解下它的几种运行模式。
运行模式
php-fpm支持三种运行模式,分别为static
、ondemand
、dynamic
,默认为dynamic
。
static
: 静态模式,启动时分配固定的worker进程。
ondemand
: 按需分配,当收到用户请求时fork worker进程。
dynamic
: 动态模式,启动时分配固定的进程。伴随着请求数增加,在设定的浮动范围调整worker进程。
这三种模式各有千秋,大家可以根据不同的环境调整相应的配置。
下面进入本文主题,着重介绍php-fpm运行原理。
运行原理
php-fpm采用master/worker架构设计,前面简单地描述master和worker进程模块的功能。下面将详细讲解这两个模块的运行原理。
master进程
master进程工作流程分为4个阶段,如下图:
1. cgi初始化阶段:分别调用fcgi_init()
和 sapi_startup()
函数,注册进程信号以及初始化sapi_globals全局变量。
2. php环境初始化阶段:由cgi_sapi_module.startup
触发。实际调用php_cgi_startup
函数,而php_cgi_startup内部又调用php_module_startup
执行。 php_module_startup主要功能:a).加载和解析php配置;b).加载php模块并记入函数符号表(function_table);c).加载zend扩展 ; d).设置禁用函数和类库配置;e).注册回收内存方法;
3. php-fpm初始化阶段:执行fpm_init()
函数。负责解析php-fpm.conf文件配置,获取进程相关参数(允许进程打开的最大文件数等),初始化进程池及事件模型等操作。
4. php-fpm运行阶段:执行fpm_run()
函数,运行后主进程发生阻塞。该阶段分为两部分:fork子进程 和 循环事件。fork子进程部分交由fpm_children_create_initial
函数处理( 注:ondemand
模式在fpm_pctl_on_socket_accept
函数创建)。循环事件部分通过fpm_event_loop
函数处理,其内部是一个死循环,负责事件的收集工作。
worker进程
worker进程分为 接收客户端请求、处理请求、请求结束三个阶段。
1. 接收客户端请求:执行fcgi_accept_request
函数,其内部通过调用accept
函数获取客户端请求。
//请求锁
FCGI_LOCK(req->listen_socket);
req->fd = accept(listen_socket, (struct sockaddr *)&sa, &len);
//释放锁
FCGI_UNLOCK(req->listen_socket);
从上面的代码,可以注意到accept之前有一个请求锁的操作,这么设计是为了避免请求出现“惊群”的现象。当然,这是一个可选的选项,可以取消该功能。
2. 处理请求阶段:首先,分别调用fpm_request_info
、php_request_startup
获取请求内容及注册全局变量($_GET
、$_POST
、$_SERVER
、$_ENV
、$_FILES
);然后根据请求信息调用php_fopen_primary_script
访问脚本文件;最后交给php_execute_script
执行。php_execute_script
内部调用zend_execute_scripts
方法将脚本交给zend引擎处理。
3. 请求结束阶段:执行php_request_shutdown
函数。此时 回调register_shutdown_function
注册的函数及__destruct()
方法,发送响应内容、释放内存等操作。
总结
php-fpm采用master/worker架构设计, master进程负责CGI、PHP公共环境的初始化及事件监听操作。worker进程负责请求的处理功能。在worker进程处理请求时,无需再次初始化PHP运行环境,这也是php-fpm性能优异的原因之一。