PHP 内存溢出错误解决,以及对 PHP 命令行和Web访问两种执行方式的理解

开发过程中,某个接口由于从数据库读取数据量过大,返回状态为 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 方法不会发挥作用,自然也不会有后续的日志记录、错误页面显示等流程。

参见手册:http://php.net/manual/zh/function.pcntl-signal.php

另外在开发中注意到一个现象:通过 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节点等配置):

  1. 请求静态资源不需要 FastCGI 处理,直接转到相应文件位置
  2. 动态请求需要 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,完成实际业务逻辑。

你可能感兴趣的:(服务端)