开发过程中,某个接口由于从数据库读取数据量过大,返回状态为 200,但无响应数据,php错误日志里有如下信息:php fatal error: allowed memory size of 134217728 bytes exhausted。
很显然这是内存溢出(out of memory)引发的错误,但是令我疑惑的是,yii 框架的业务日志(application.log)里没有任何输出,页面上也没有 stack trace 的错误信息,于是对这个原因进行追查。
原因如下,先看 yii 框架 capplication.php 文件核心代码:
public function run() { if($this->haseventhandler('onbeginrequest')) $this->onbeginrequest(new cevent($this)); register_shutdown_function(array($this,'end'),0,false); $this->processrequest(); if($this->haseventhandler('onendrequest')) $this->onendrequest(new cevent($this)); }
在处理请求前使用了 register_shutdown_function 注册异常终止时的回调,正常来说,php 出现异常脚本终止时会回调 end() 方法,在 onendrequest 事件的监听器中可以使用 error_get_last() 获取到本次错误。
但是,当 oom 发生时,linux out of memory killer 会执行 kill -9 发送 sigkill 信号,根据 php 手册中的说明, sigkill 信号无法捕获和拦截,php 脚本会直接退出,任何清理代码都不会执行,所以 register_shutdown_function 方法不会发挥作用,自然也不会有后续的日志记录、错误页面显示等流程。
另外在开发中注意到一个现象:通过 web 访问会出现 oom,但通过 console 执行就不会报错。
由此可见两种方式是有区别的,web 访问时,php 脚本进程由 php-fpm启动,还要受 fpm 配置文件限制,/etc/php-fpm.d里配置文件有 php_admin_value[memory_limit] = 128m 限制。所以通过 web 访问时,仅增加 php.ini 中的 memory_limit 无效。
而 console 方式执行不经过 php-fpm,所以仅受 php.ini 中配置的内存参数限制,而开发机中配置的 memory_limit => 512m => 512m,所以此时不会产生 oom。
这里有个疑问,从实现原理的角度,php-fpm 是如何对 php 进程管理的?php-fpm 真的会用 kill -9 杀死 php 脚本进程么?
附上 webserver、php-fpm、php 脚本的调用关系:
请求首先进入 web 服务器(如 nginx),nginx 分发请求(依据server节点、location节点等配置):
请求静态资源不需要 fastcgi 处理,直接转到相应文件位置
动态请求需要 php 代码处理,则需要把请求交给实现了 fastcgi 协议的程序(php-fpm)
可以在nginx看到这样的配置信息:“fastcgi_pass 127.0.0.1:9000;”,执行命令“lsof -i:9000”可以看到9000端口刚好是php-fpm进程。
nginx 将请求信息传给了 php-fpm,php-fpm 分配一个 worker 进程处理,worker 进程注册变量 $_get/$_post 等,根据请求信息访问指定的 php 脚本文件,然后使用 php 解释器执行。
(我的理解是:相当于 php-fpm 启动了 php 解释器,有点像执行了 php -f script.php 命令)
网络请求的信息层层传递,最终到达 php,所以在 php 代码里可以获取到本次 http 请求的各种参数。
执行哪个 php 脚本由 nginx 告诉 php-fpm,在 nginx 配置中可见一行:
fastcgi_param script_filename /home/dev_user/www/xxx/webroot/index.php;
若不指定 php 文件,就会使用该默认配置,尝试使用根目录下的 index.php 文件。index.php 里会启动框架程序,由框架找到对应的 controller 和 action,完成实际业务逻辑。
以上就是php内存溢出、命令行和web服务两种执行方式的理解的详细内容。
