Ускорение приложения PHP - PullRequest
2 голосов
/ 21 января 2010

У меня есть список данных, которые необходимо обработать. Вот как это работает прямо сейчас:

  • Пользователь нажимает кнопку процесса.
  • PHP-код берет первый элемент, который необходимо обработать, обрабатывает его за 15-25 секунд, переходит к следующему элементу и т. Д.

Это занимает слишком много времени. Вместо этого я хотел бы, чтобы это было:

  • Пользователь нажимает кнопку процесса.
  • PHP-скрипт берет первый элемент и начинает его обрабатывать.
  • Одновременно другой экземпляр скрипта берет следующий элемент и обрабатывает его.
  • И так далее, примерно 5-6 элементов обрабатываются одновременно, и мы получаем 6 элементов, обрабатываемых за 15-25 секунд вместо одного.

Возможно ли что-то подобное?

Я думал, что использую CRON для запуска экземпляра скрипта каждую секунду. Все элементы, которые должны быть обработаны, будут помечены как таковые в базе данных MySQL, поэтому всякий раз, когда экземпляр запускается через CRON, он просто берет следующий элемент, помеченный для обработки, и удаляет флаг.

Мысли

Редактировать: Чтобы прояснить что-то, каждый «элемент» хранится в таблице базы данных mysql в виде отдельных строк. Всякий раз, когда начинается обработка элемента, он помечается как обрабатываемый в БД, поэтому каждый новый экземпляр просто захватывает следующую строку, которая не обрабатывается, и обрабатывает ее. Следовательно, мне не нужно указывать элементы в качестве аргументов командной строки.

Ответы [ 7 ]

6 голосов
/ 21 января 2010

Вот одно решение, не самое лучшее, но отлично подойдет для 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

<code><?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 "
"; ?>

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, чтобы заставить это работать.

Очень весело, я могу найти применение позже.

5 голосов
/ 21 января 2010

Используйте внешнюю рабочую очередь, такую ​​как Beanstalkd , которую ваш PHP-скрипт также записывает в кучу работ. У вас есть как можно больше рабочих процессов, которые извлекают задания из beanstalkd и обрабатывают их максимально быстро. Вы можете раскрутить столько рабочих, сколько у вас есть памяти / процессора. Ваше тело работы должно содержать как можно меньше информации, возможно, только некоторые идентификаторы, которыми вы пользуетесь БД. beanstalkd имеет множество клиентских API и сам имеет очень простой API, думаю, memcached.

Мы используем beanstalkd для обработки всех наших фоновых заданий, мне это нравится. Простой в использовании, очень быстрый.

1 голос
/ 21 января 2010

Можете ли вы реализовать потоки в JavaScript на стороне клиента? мне кажется, я видел библиотеку javascript (возможно, от google?), которая ее реализует. Google, и я уверен, что вы найдете что-нибудь. Я никогда не делал этого, но я знаю, что это возможно. в любом случае, ваш клиентский javascript может активировать (ajax) сценарий php один раз для каждого элемента в отдельных потоках. это может быть проще, чем пытаться сделать все это на стороне сервера.

-don

1 голос
/ 21 января 2010

В PHP нет многопоточности, однако вы можете использовать fork.

php.net: pcntl-fork

Или вы можете выполнить команду system ()и запустите другой процесс, который является многопоточным.

0 голосов
/ 26 января 2010

Для решения этой проблемы я использовал два разных продукта; Gearman и RabbitMQ.

Преимущество размещения ваших заданий в каком-либо программном обеспечении для организации очередей, например Gearman или Rabbit, заключается в том, что у вас есть несколько машин, и все они могут участвовать в обработке элементов вне очереди.

Gearman проще в настройке, поэтому я бы посоветовал сначала немного поэкспериментировать с ним. Если вы найдете что-то более тяжелое с надежностью очереди; Посмотрите на RabbitMQ

0 голосов
/ 21 января 2010

Если у вас PHP-сервер с большим трафиком, вы ВНУТРЕННИЙ , если не используете альтернативный кэш PHP: http://php.net/manual/en/book.apc.php. Вам не нужно вносить изменения в код для запуска APC.

Другой полезный метод, который может работать вместе с APC, - это использование системы шаблонов Smarty, которая позволяет вам кэшировать вывод, чтобы страницы не приходилось перестраивать.

0 голосов
/ 21 января 2010

Вы можете использовать pcntl_fork () и family для ветвления процесса - однако вам может понадобиться что-то вроде IPC, чтобы сообщить родительскому процессу о том, что дочерний процесс (тот, который вы создали) завершен.

Вы можете записать их в общую память, например, через memcache или DB.

Вы могли бы также сделать так, чтобы дочерний процесс записывал завершенные данные в файл, который родительский процесс продолжает проверять - по мере того, как каждый дочерний процесс завершает, файл создается / записывается / обновляется, и родительский процесс может получить его по одному время, и они бросают их обратно к вызываемому абоненту / клиенту.

Работа родителя заключается в том, чтобы контролировать очередь, следить за тем, чтобы одни и те же данные не обрабатывались дважды, а также проверять исправность дочерних элементов (лучше убить этот процесс и начать все сначала ... и т. Д.)

Что-то еще нужно иметь в виду - на платформах Windows вы будете строго ограничены - я даже не думаю, что у вас есть доступ к pcntl_, если вы не скомпилировали PHP с поддержкой для него.

Кроме того, вы можете кэшировать данные после того, как они были обработаны, или это уникальные данные каждый раз? что, несомненно, ускорит процесс ..?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...