加速PHP应用程序

时间:2010-01-21 01:08:50

标签: php mysql performance multithreading

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

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

这需要太长时间。我想要的是:

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

这样的事情可能吗?

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

思想?

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

7 个答案:

答案 0 :(得分:6)

这是一个解决方案,不是最好的解决方案,但在Linux上运行良好:

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

  • 命令行输入包括`$ id`和`$ item`
  • 脚本将其PID写入`/ tmp / $ id。$ item.pid中的文件。
  • 脚本echos结果为XML或可以读入PHP到stdout的东西
  • 完成后,脚本会删除`/ tmp / $ id。$ item.pid`文件

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

  • `exec(“nohup php myprocessing.php $ id $ item> /tmp/$id.$item.xml”);`for each items
  • 轮询`/ 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来实现这个目的。

很多乐趣,我可能会在以后找到它。

答案 1 :(得分:5)

使用像Beanstalkd这样的外部工作队列,您的PHP脚本也会编写一堆作业。您有尽可能多的工作进程从beanstalkd中提取作业并尽快处理它们。你可以像拥有内存/ CPU那样增加工作量。您的工作机构应该包含尽可能少的信息,可能只是您点击数据库的一些ID。 beanstalkd有很多客户端API,它本身有一个非常基本的API,想想memcached。

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

答案 2 :(得分:1)

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

php.net:pcntl-fork

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

答案 3 :(得分:1)

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

-don

答案 4 :(得分:0)

如果您正在运行高流量的PHP服务器,那么如果您不使用备用PHP缓存,则 INSANE http://php.net/manual/en/book.apc.php。您不必进行代码修改即可运行APC。

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

答案 5 :(得分:0)

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

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

Gearman更容易设置,所以我建议先稍微探讨一下。如果你发现你需要更重要的东西和队列稳健性;看看RabbitMQ

答案 6 :(得分:-1)

你可以使用pcntl_fork()和family来分叉一个进程 - 但是你可能需要像IPC这样的东西来回传给子进程(你分叉的那个)完成的父进程。

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

您还可以让子进程将已完成的数据写入文件,父进程一直在检查 - 当每个子进程完成时,创建/写入/更新文件,父进程可以抓取它,一个在时间,他们把它们扔回被叫/客户端。

父母的工作是控制队列,确保相同的数据不会被处理两次,并且还要检查孩子们的状态(更好地杀死那个失控的过程并重新开始等等)

要记住的其他事项 - 在Windows平台上你将受到严重限制 - 我甚至认为你无法访问pcntl_,除非你编译PHP并支持它。

此外,您可以在处理完数据后对其进行缓存,还是每次都是唯一数据?这肯定会加快速度..?