【问题标题】:Executing php file from another php file uses too much CPU从另一个 php 文件执行 php 文件占用过多 CPU
【发布时间】:2018-09-23 00:45:31
【问题描述】:

我已经阅读了关于 SO 的其他类似标题的问题,但这不是这个问题的主题。我知道如何从另一个 PHP 脚本执行 PHP 脚本。问题是,当我这样做时,它使用了太多的 CPU。我想知道如何减少这种情况。

我有一个简单的类似前端控制器的脚本,名为 index.php。它处理来自客户端的 GET 请求,并根据传递的“action”参数,将请求发送到适当的文件以进行处理。例如,这是一个客户端请求:

xhttp.open("GET", serverURL + "?action=doSomething" + "&userID=" + user.ID + "&time=" + lastServerTime, true);

index.php 有一个将“action”参数映射到相应文件的数组:

exec('php ' . $url_map[$action] . ' "' . $parameter1 . '"' . ' "' . $parameter2 . '" 2>&1', $output, $return_value);

出于测试目的,我创建了一个 PHP 脚本,它除了测量 CPU 利用率并将其转储到日志文件之外什么都不做:

<?php

function varDumpToFile($parameter1) {
    $file = 'log.txt';
    $dump = $parameter1;

    $output = print_r($dump, true);

    file_put_contents($file, $output, FILE_APPEND | LOCK_EX);
}

varDumpToFile(`ps -eo pcpu,pid,user,args --no-headers| sort -t. -nk1,2 -k4,4 -r |head -n 5`);

?>

这会生成一个如下所示的日志文件:

9.0 3123052 user   /opt/cpanel/ea-php56/root/usr/bin/php cputest.php 10 147424 1537625595

显然,PHP 脚本不应该占用 9% 的 CPU 来执行。为了比较,我运行了相同的脚本,通过 GET 请求直接访问它:

0.1 3186198 user   lsphp:ic_html/dev/php/cputest.php

0.1% 更喜欢它。但是为什么从另一个 PHP 脚本调用这个 PHP 脚本会占用这么多 CPU 呢?是不是因为我在执行 PHP 时必须执行 PHP 的“新实例”,这会产生很多开销?如果是这样,有没有办法使用“已经运行”的 PHP 实例来执行 PHP 脚本?或者有其他方法吗?

【问题讨论】:

  • 您是否有特定原因要使用 CLI 执行 PHP 文件?我以前从未见过有人这样做,这是一个严重的安全风险。除非您进行大量输入验证,否则有人会对您的网站做一些讨厌的事情。
  • 通过 exec 调用它的原因是什么?为什么要考虑 CPU 使用率?您想更快地运行该过程吗?被调用的脚本做了哪些工作?如果您需要从 HTTP 请求周期中卸载在 exec() 方法中完成的工作,请查看消息队列系统,如 RabbitMq、ZeroMq
  • 感谢大家的cmets。 Rob:我想这样做有一个非常具体的原因——被调用的文件必须在后台运行,而 index.php 必须立即响应 HTML 客户端。我从大量阅读 SO 中得到的理解是,调用“后台”PHP 文件是同时拥有这两者的唯一方法:在没有 index.php 脚本“等待”后台脚本在回复之前结束的情况下即时响应客户端客户端(可能需要几分钟),同时让后台脚本在后台愉快地运行而不会打扰任何人。
  • mblaettermann:我没有任何具体理由通过 exec 调用它,除非我认为这是正确的做法。我正在考虑 CPU 使用率,因为它限制了客户端可用的资源。每个客户端约 10%,即只有 10 个并发客户端。我不需要进程运行得更快(它在 1 毫秒内执行),但它确实需要使用更少的 CPU。感谢您的其他建议,但这是一个已经存在的具有大型代码库的应用程序,实际上运行得很好(除了这个问题)。我不会为了解决一个问题而改变整个架构。

标签: php exec


【解决方案1】:

我总是说“如有疑问,请查看 PHP 源代码”。 In here,例如。在执行exec 时,您必须分叉进程、创建新流、从输入缓冲区读取等。

而且,虽然 PHP 是一种编译语言,但对于新分叉的进程,您必须运行操作码编译器以生成操作码(类似于 Java 字节码的指令),然后执行这些操作。您可以阅读有关它的所有信息here。最后你运行编译器两次,分别为每个 fork。

它值你的 CPU 的 9% 吗?我不知道。也许。也许不吧。谁知道呢。

“更好的解决方案”?升级到最新版本的 PHP。不再支持 PHP 5.6,安全更新将在 3 个月后停止。更好的解决方案 - 在不使用 exec 的情况下保持正常的面向对象且可维护的代码。 IMO,可以像你一样玩exec。但是,如果它是您的生产代码,我为那些将在您之后维护您的代码的人祈祷。

【讨论】:

  • 谢谢你 - 我明白为什么会这样浪费资源。这绝对不值我 9% 的 CPU。正如您从我上面的测试中看到的,0.1% 的 CPU 是我所期望(和渴望)的。升级到 PHP 7 肯定在我的待办事项清单上,但我需要先解决这个问题,然后才能这样做。此外,如果我的代码一开始就没有正确编写,我认为最新版本的 PHP 不会给我所需的性能提升。是否有另一种更好的方式在后台运行 PHP 脚本,从现有的 PHP 脚本调用,同时保持对客户端的即时响应?
  • 也许你应该重新分析你所做的事情的目的?如果不将项目视为一个整体,就很难说...例如,mblaettermann 建议使用amqp 系统。根据实现,它也会创建一个新进程,但允许管理队列,并发用户不会死。或者,也许您只需要重新定义您所做的一半事情。为了给你一个好的解决方案,我必须自己看代码¯\_(ツ)_/¯
  • 谢谢亚历克斯。我将您的答案标记为正确,因为它确实回答了我的问题,即为什么执行一个新的 PHP 文件会占用如此多的 CPU(当我不想要它时)。我目前正在测试的解决方案是将前端控制器中的 exec 替换为 include_once。从最初的测试来看,它肯定会使 CPU 利用率回落到 0.1% 大关。它是否还允许我在后台运行 PHP 脚本,我不确定。如果没有,那么我一定会看看建议的替代方法。
【解决方案2】:

无论您以mod_phpfpm 哪种方式运行应用程序,它们都依赖于准备好工作进程来管理您的请求。流程管理是内置的:他们将尽最大努力让您指定的尽可能多的工作人员处于空闲状态,并重用它们来避免这个问题,不得不在最不理想的时刻派生进程。

不仅执行新进程会产生开销,而且执行环境也会完全不同。如果您查看您的 php 配置,将会有几个 php.ini 文件,每个文件对应一个特定的环境。这意味着一个环境可以完全启用不同的模块或不同的配置。将 cli 脚本 max_execution_timememory_limit 设置为无限制的情况并不少见。这可能会影响服务器上的资源使用,但维护起来也很麻烦。

此外,由于您的脚本将在不同执行环境中的全新进程中运行,因此无法访问某些变量(如 $_SERVER$_POST)或发送标头等功能。

还有一种叫做共享内存的东西。正如@Alex 提到的,必须编译脚本。如果您启用了操作码缓存(您应该启用),则字节码在编译时会被缓存,并且如果生成的字节码已经存在,则可以跳过此编译过程。为此,您需要拥有一个持久运行的进程来保留此内存。如果您正在创建一个新进程,则它无法访问此共享区域,并且必须自己进行编译。

【讨论】:

  • 关于执行环境和共享内存的优点。虽然我不依赖这些全局变量来完成我的工作,但我将来可能需要这样做,所以我可以看到为什么分叉一个新的 PHP 进程是不可取的。我的 max_execution_time 和 memory_limit 设置得非常低,因此不会出现超时或内存不足的情况。我问亚历克斯,是否有另一种更好的方式在后台运行 PHP 脚本,从现有的 PHP 脚本调用,同时保持对客户端的即时响应?
猜你喜欢
  • 2019-06-23
  • 1970-01-01
  • 2017-12-27
  • 1970-01-01
  • 2016-04-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多