【问题标题】:Web Server CPU/Memory Resource LImited as a result of multiple ajax calls to PHP script由于对 PHP 脚本的多次 ajax 调用,Web 服务器 CPU/内存资源受限
【发布时间】:2013-02-20 03:58:33
【问题描述】:

我正在使用 Webhostingpad 托管一个网站,但遇到了问题。当我的主页加载时,我目前正在对返回用于主页的内容的 php 脚本进行 8 次并发 ajax 调用。 8 个 ajax 调用正在调用一个名为 run.php 的文件。这个文件的工作只是从一个名为 amazon 的类中调用一个函数,该类在另一个名为 amazon.php 的文件中定义。

这是通过 ajax 调用 8 次的 URL。 8 个调用之间的唯一区别是查询字符串:

http://my-domain.com/run.php?f=getItemsById&arg=id:B0043OYFKU,B001JKTTVQ,B004Y9D90Q,B003S516XO,B002XQ1YTK,B003V265QW,B00121UVU0,B004EDYQUE,B000P22TIY,B000E7WHLY

如您所见,我在 url 的“f”参数中传递函数名称。

run.php 文件如下所示:

require_once('amazon.php');

$function_name = $_REQUEST['f'];
$arg_parameter = $_REQUEST['arg'];

$arg_tmp = explode(";", $arg_parameter);
$arg_array = array();
foreach($arg_tmp as $key_value_pair){
    $exploded = explode(':', $key_value_pair);
    $key = $exploded[0];
    $value = $exploded[1];
    $arg_array[$key] = $value;
}

$amazon = new amazon();
echo $amazon->$function_name($arg_array);

如您所见,这个文件只是从 amazon.php 调用一个函数并回显结果,因此我可以在 ajax 函数的回调中使用它。

这是来自 amazon.php 关于getItemsById() 函数的相关代码:

class amazon {

    private $url;
    private $accessKey = 'AKIAISJ2OHTBA888311SD';
    private $secretAccessKey = 'RM8EG61w3dLwjymtAEVdfsdiesd883711lskdf';

    function __construct(){
        $this->url = 'http://webservices.amazon.com/onca/xml?Service=AWSECommerceService&AWSAccessKeyId=' . $this->accessKey . '&AssociateTag=global-18&Version=2011-08-01';
    }

    public function getItemsById($args = array()){

        $itemIds = $args['id'];

        $url = $this->url;
        $url .= '&Operation=ItemLookup';
        $url .= '&ItemId=' . $itemIds;
        $url .= '&ResponseGroup=Images,Small,Offers,VariationSummary,EditorialReview';

        $signedUrl = $this->amazonSign($url, $this->secretAccessKey);
        $returned_xml = file_get_contents($signedUrl);
        return $returned_xml;

    }
}

正如您在上面看到的,此函数正在调用 amazon.com 的 API 的 URL,并使用 PHP 的 file_get_contents() 函数返回 XML。我的问题是对 run.php 进行的一些 ajax 调用已成功执行,而另一些则得到 HTTP 500 内部服务器错误。当我在本地服务器上运行它时,它工作正常。当我在办公室的开发服务器上运行它时,它运行良好。但是,我一直在我的 Webhostingpad 服务器上看到这个问题。一些 ajax 调用返回 HTTP 500 错误。

我已与 Webhostingpad 支持人员交谈过,他们提供给我的唯一见解是我超出了我的 CPU/内存资源限制。来自服务器的错误日志似乎证实了这一点:

[2013 年 2 月 19 日星期二 21:36:39] [错误] [客户端 68.174.126.115] (12)不能 分配内存:无法创建子进程:/opt/suphp/sbin/suphp 对于/home/my-server/public_html/my-domain.com/run.php,推荐人: http://my-domain.com/

我向社区提出的问题是,这里是否有明显的内存密集型问题?我觉得我正在做的事情并没有那么出乎意料,所以我想弄清楚我是否应该专注于优化我的脚本,或者我是否应该只是寻找另一个托管服务提供商。

【问题讨论】:

  • 我猜测一台共享服务器上有太多网站。
  • 呼叫必须是并发的吗?或者您是否可以简单地执行第一个 AJAX 调用,等到它完成,将本地数据存储在数据结构中,执行第二个调用,冲洗,重复?浏览器中执行 Ajax 调用的对象将在完成后通知您。
  • 谢谢@kermit。我以前让 ajax 调用同步工作(我知道这令人不悦,这就是我更改它的原因),但这基本上模仿了您所描述的相同行为,即一个调用仅在另一个调用完成后发生。主要问题是它比同时进行所有 8 个调用并在它们全部完成时使用延迟对象执行操作要慢得多。
  • 嗯...另一种选择是在一次 Ajax 调用中发送所有需要的数据,这样只有一个执行 PHP 脚本的实例。为您的类实现单例模式(以便只创建一次实例 - 基本上这涉及一个私有构造函数和一个检查是否创建类的函数。如果是,则返回对该类的引用,如果不是类被构造并返回一个引用)。我认为一次性交易应该减少开销,而不是 8 个单独的调用。
  • Yeesh - 单例模式曾经出现在 PHP.net 网站上,但现在只有德语版本。但是第二个例子——最重要的例子——是用英语写的。不知道那里发生了什么:php.net/manual/de/language.oop5.patterns.php - 暂时不需要担心实现克隆或唤醒方法。这些很好,但不是测试所必需的。

标签: php ajax memory cpu file-get-contents


【解决方案1】:

如果不能选择同步执行 AJAX 请求,您仍然可以对 PHP 脚本的内存消耗做一些事情。目前,PHP 脚本在将亚马逊返回的整个 XML 回显到客户端之前,会将其保存在内存中。如果 XML 很大并且您同时执行 8 次,内存不足并不奇怪。

一种解决方案是使用 cURL 向亚马逊发出请求,并使用 cURL 选项 CURLOPT_WRITEFUNCTION 将结果回显给客户端。这样,您可以使 amazon.php 脚本将结果 XML 流式传输到客户端,并且它不会使用太多内存。

amazon.php 示例代码:

<?
function writeCallback($handle, $data)
{
    echo $data;    
    ob_flush();
    flush();
    return strlen($data);
}

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, 'http://webservices.amazon.com/onca/xml');
curl_setopt($ch, CURLOPT_WRITEFUNCTION, 'writeCallback');

ob_start(); // start output buffer
curl_exec($ch); // commence streaming

【讨论】:

  • 非常感谢 Matthijs 的建议。我只是想确保我完全理解这里发生了什么,因为我已经按照你的建议实现了它,就我收到的内存错误而言,它没有任何区别。您是否有理由使用回显数据的回调函数,而不是使用选项 CURLOPT_RETURNTRANSFER 并将 curl_exec 的输出设置为变量。另外,为什么回调函数返回strlen($data)?基本上,我正试图准确了解它应该如何工作,因为目前它没有任何作用。
  • 这样做的主要原因是流式传输您从亚马逊收到的数据,而不是一次性将其全部存入内存。如果您查看 CURLOPT_WRITEFUNCTION 和 CURLOPT_RETURNTRANSFER 的文档,您会看到 CURLOPT_WRITEFUNCTION 允许您这样做。
  • 所有这一切的一个重要部分是您不要在 writeCallback 中缓冲数据,因为这违背了目的。您必须将每条数据无缓冲地发送出去。它可能要求您在每次回显后调用ob_flush()。我已经更新了上面的代码以反映这一点。但是,这可能仍然不起作用,因为您的网络服务器也可能缓冲输出,即使您的 PHP 脚本没有。例如,当启用 Apache 的 mod_deflate 时,可能会发生这种情况。
  • 再次感谢马蒂斯。这似乎也没有帮助,你指出它仍然可能没有。我在这里查看了 CURLOPT_WRITEFUNCTION 的文档:php.net/manual/en/function.curl-setopt.php,但我没有看到您建议的任何关于 streaming 的信息。如果您可以提供更多见解,我将不胜感激,但看起来我可能必须升级我的托管计划以拥有专用内存和 CPU 资源。另外,您能解释一下为什么您的 writeCallback 函数返回 strlen($data) 吗?这只是某种约定吗?
  • 下面的 CURLOPT_WRITEFUNTION 的 php.net 文档。请以粗体查看最后一个问题的答案: CURLOPT_WRITEFUNCTION 回调函数的名称,其中回调函数有两个参数。第一个是 cURL 资源,第二个是包含要写入的数据的字符串。必须使用此回调函数保存数据。 它必须返回写入的确切字节数,否则传输将因错误而中止。
猜你喜欢
  • 2011-11-06
  • 2017-11-06
  • 1970-01-01
  • 2012-05-24
  • 2016-12-13
  • 1970-01-01
  • 2016-02-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多