【问题标题】:Speeding up a PHP App加速 PHP 应用程序
【发布时间】:2010-01-26 13:26:26
【问题描述】:

我有一个需要处理的数据列表。它现在的工作方式是这样的:

  • 用户单击进程按钮。
  • PHP 代码获取需要处理的第一个项目,需要 15-25 秒来处理它,然后转到下一个项目,依此类推。

这花费的时间太长了。我想要的是:

  • 用户单击进程按钮。
  • PHP 脚本获取第一项并开始处理它。
  • 同时脚本的另一个实例获取下一项并对其进行处理。
  • 以此类推,大约有 5-6 个项目正在同时处理,我们在 15-25 秒内处理了 6 个项目,而不是一个。

这样的事情可能吗?

我在想我使用 CRON 每秒启动一个脚本实例。所有需要处理的项目都将在 MySQL 数据库中进行标记,因此每当通过 CRON 启动实例时,它只会获取下一个标记为要处理的项目并删除该标记。

想法?

编辑:为了澄清一些事情,每个“项目”作为单独的行存储在 mysql 数据库表中。每当对一个项目开始处理时,它就会在数据库中被标记为正在处理,因此每个新实例都将简单地抓取下一个未被处理的行并对其进行处理。因此,我不必将这些项目作为命令行参数提供。

【问题讨论】:

  • 你正在处理多少项目,你在地球上处理什么?
  • 您的服务器运行在什么操作系统上? (希望他们都是 linux 或 unix
  • 为什么每个项目需要 15-25 秒?如果是因为 CPU 已用尽,那么任何类型的多线程解决方案都会使其花费更长的时间。如果是因为磁盘 IO 或网络带宽是瓶颈,多线程不会让它变得更快。您是否对应用程序进行了概要分析,是否知道当前的瓶颈是什么,以便避免将缓慢的应用程序变成更复杂且更难维护的缓慢应用程序?
  • 你不能每秒启动 CRON 项目。即使你可以,也没有托管服务提供商会让你这么做。见linuxquestions.org/questions/programming-9/…
  • 请回答 mattjames 的问题,因为它们会为系统提供很多信息。您是否分析过您的代码以找出瓶颈所在?下面的所有解决方案都增加了很多复杂性和额外的代码(以及额外的错误),所以我会先投入更多的时间来分析和优化单线程案例。

标签: php mysql performance multithreading


【解决方案1】:

这是一个解决方案,不是最好的,但在 Linux 上可以正常工作:

将处理PHP拆分成单独的CLI脚本,其中:

  • 命令行输入包括`$id`和`$item`
  • 脚本将其 PID 写入 `/tmp/$id.$item.pid` 中的文件
  • 脚本将结果作为 XML 或可以读入 PHP 的内容回显到标准输出
  • 完成后,脚本会删除 `/tmp/$id.$item.pid` 文件

您的主脚本(可能在您的网络服务器上)可以:

  • `exec("nohup php myprocessing.php $id $item > /tmp/$id.$item.xml");` 对于每个项目
  • 轮询 `/tmp/$id.$item.pid` 文件直到所有文件都被删除(睡眠/检查轮询就足够了)
  • 如果它们从未被删除,则终止所有处理脚本并报告失败
  • 如果成功从 `/tmp/$id.$item.xml` 读取格式/输出给用户
  • 如果您不想缓存以供以后使用,请删除 XML 文件

后台 nohup 启动的应用程序将独立于启动它的脚本运行。

这让我很感兴趣,所以我决定写一个 POC。

test.php

<?php
$dir =  realpath(dirname(__FILE__));
$start = time();

// Time in seconds after which we give up and kill everything
$timeout = 25;

// The unique identifier for the request
$id = uniqid();

// Our "items" which would be supplied by the user
$items = array("foo", "bar", "0xdeadbeef");

// We exec a nohup command that is backgrounded which returns immediately
foreach ($items as $item) {
    exec("nohup php proc.php $id $item > $dir/proc.$id.$item.out &");
}

echo "<pre>";
// Run until timeout or all processing has finished
while(time() - $start < $timeout) 
{
  echo (time() - $start), " seconds\n";
  clearstatcache();    // Required since PHP will cache for file_exists
  $running = array();
  foreach($items as $item)
  {
      // If the pid file still exists the process is still running    
      if (file_exists("$dir/proc.$id.$item.pid")) {
          $running[] = $item;
      }
  }
  if (empty($running)) break;
  echo implode($running, ','), " running\n";
  flush();
  sleep(1);  
}

// Clean up if we timeout out
if (!empty($running)) {
    clearstatcache();
    foreach ($items as $item) {
        // Kill process of anything still running (i.e. that has a pid file)
        if(file_exists("$dir/proc.$id.$item.pid") 
            && $pid = file_get_contents("$dir/proc.$id.$item.pid")) {
            posix_kill($pid, 9);                
            unlink("$dir/proc.$id.$item.pid");
            // Would want to log this in the real world
            echo "Failed to process: ", $item, " pid ", $pid, "\n";
    }
    // delete the useless data
    unlink("$dir/proc.$id.$item.out");
    }
} else {
    echo "Successfully processed all items in ", time() - $start, " seconds.\n";
    foreach ($items as $item) {
    // Grab the processed data and delete the file
        echo(file_get_contents("$dir/proc.$id.$item.out"));
        unlink("$dir/proc.$id.$item.out");
    }
}
echo "</pre>";
?>

proc.php

<?php
$dir =  realpath(dirname(__FILE__));
$id = $argv[1];
$item = $argv[2];

// Write out our pid file
file_put_contents("$dir/proc.$id.$item.pid", posix_getpid());

for($i=0;$i<80;++$i)
{
    echo $item,':', $i, "\n";
    usleep(250000);
}

// Remove our pid file to say we're done processing
unlink("proc.$id.$item.pid");

?>

将 test.php 和 proc.php 放在你服务器的同一个文件夹中,加载 test.php 并享受。

你当然需要 nohup (unix) 和 PHP cli 才能让它工作。

很有趣,我以后可能会发现它的用处。

【讨论】:

  • 一个不错的解决方案,我唯一关心的是 nohup,如何检查我的服务器是否安装了这个?我不需要通过命令行传递参数,它们存储在数据库中,因此proc.php 文件只会从数据库中获取下一个待处理项目并进行处理,不需要传递任何参数。假设这就是我不需要的 php cli 所需要的。
  • nohup 是 coreutils 软件包的一部分,其中包括 lscd 等内容。如果任何 Linux 服务器没有它,我会感到惊讶。 nohup 所做的只是:“运行不受挂断影响的命令,输出到非 tty”。由于 exec 可以像使用 shell 一样有效地运行事物,因此您需要 PHP 二进制文件才能运行您的 PHP 脚本。如果不是,那么有一种解决方法,就像安装了 wget 一样,您可以执行相同的技巧,但从您的服务器中提取网页(如果可能的话,您真的不想这样做)。
【解决方案2】:

使用像 Beanstalkd 这样的外部工作队列,您的 PHP 脚本也会编写大量作业。您有尽可能多的工作进程从 beanstalkd 中提取作业并尽可能快地处理它们。您可以启动与内存/ CPU 一样多的工作人员。您的工作正文应包含尽可能少的信息,也许只是您访问数据库的一些 ID。 beanstalkd 有大量的客户端 API,它本身也有一个非常基本的 API,想想 memcached。

我们使用 beanstalkd 来处理我们所有的后台作业,我喜欢它。易于使用,速度非常快。

【讨论】:

  • Gearman 也是一个很好的外部工作外包解决方案。
  • 现在你也可以用redis来做到这一点,因为它有一个阻塞弹出功能。见simonwillison.net/2010/Jan/7/blocking
  • 这可以自动/以编程方式使用吗?我不是在寻找通过命令行完成的任何事情,而是希望尽可能少地做系统管理员工作。安装看起来也很辛苦?
【解决方案3】:

PHP 中没有多线程,但是你可以使用 fork。

php.net:pcntl-fork

或者您可以执行一个 system() 命令并启动另一个多线程进程。

【讨论】:

  • 能否提供一些示例代码或更详细的 system() 函数?
【解决方案4】:

你能在客户端用javascript实现线程吗?在我看来,我已经看到了一个实现它的 javascript 库(也许来自谷歌?)。谷歌它,我相信你会找到一些东西。我从来没有做过,但我知道它是可能的。无论如何,您的客户端 javascript 可以为单独线程中的每个项目激活(ajax)一次 php 脚本。这可能比尝试在服务器端完成所有操作更容易。

-不要

【讨论】:

    【解决方案5】:

    如果您运行的是高流量 PHP 服务器,如果您不使用备用 PHP 缓存,那么您就是 INSANEhttp://php.net/manual/en/book.apc.php。您无需修改​​代码即可运行 APC。

    另一个可以与 APC 一起使用的有用技术是使用 Smarty 模板系统,它允许您缓存输出,从而不必重新构建页面。

    【讨论】:

    • 与所提出的问题无关@all
    【解决方案6】:

    为了解决这个问题,我使用了两种不同的产品; Gearman 和 RabbitMQ。

    将您的工作放入某种排队软件(如 Gearman 或 Rabbit)的好处是您拥有多台机器,它们都可以参与处理队列外的项目。

    Gearman 更容易设置,所以我建议先尝试一下。如果你发现你需要更重的队列健壮性;查看 RabbitMQ

    【讨论】:

      【解决方案7】:

      您可以使用 pcntl_fork() 和家庭来分叉一个进程 - 但是您可能需要 IPC 之类的东西来向父进程传达子进程(您分叉的那个)已经完成。

      您可以让它们写入共享内存,例如通过 memcache 或数据库。

      您还可以让子进程将完成的数据写入文件,父进程会不断检查 - 随着每个子进程完成文件的创建/写入/更新,父进程可以抓取它,一个时间,然后他们将它们扔回被调用者/客户端。

      父母的工作是控制队列,确保相同的数据不会被处理两次,并检查孩子的健全性(最好杀死那个失控的进程并重新开始......等等)

      还有一点要记住——在 Windows 平台上你将受到严重限制——我什至认为你无法访问 pcntl_,除非你编译的 PHP 支持它。

      另外,您可以在数据处理后缓存数据,还是每次都是唯一数据?那肯定会加快速度..?

      【讨论】:

      • 嗯,您并不需要 IPC 来判断子进程何时完成。您可以生成多个子进程,给每个子进程一个文件以放入其结果,然后使用pcntl-wait (php.net/manual/en/function.pcntl-wait.php) 等待子进程完成。当每个完成时,拉入其数据,并再次pcntl-wait 处理下一个,跟踪直到所有孩子都返回。
      • 是的,确实如此——我举了几个不使用 IPC 的例子(实际上我也举了一个使用文件的例子),但实际上,我们仍然在谈论共享内存。
      • 我想说是的,但应该检查 php.net - 我认为对于我们最终使用的 Windows:us.php.net/manual/en/book.com.php Ugh,COM!
      • 我不需要来回交流。我只需要一种启动另一个 php 脚本实例的方法。它只会从数据库中获取标记为需要处理的下一个项目并对其进行处理。对如何完成有任何想法?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-09-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多