Масштабируемая, отложенная обработка PHP - PullRequest
19 голосов
/ 25 июня 2010

Я работаю над онлайн-приложением PHP, для которого требуется отложенное событие PHP.По сути, мне нужно иметь возможность выполнять произвольный код PHP через много секунд (но это могут быть дни) после первоначального обращения к URL.Мне нужно довольно точное выполнение этих событий PHP, а также я хочу, чтобы оно было достаточно масштабируемым.Я пытаюсь избежать необходимости планировать работу cron каждую секунду.Я изучал Gearman , но, похоже, он не дает возможности планировать события, и, как я понимаю, PHP не предназначен для работы в качестве демона.

Было бы идеально, если бы я мог указать какому-либо внешнему процессу опросить URL-адрес «средства проверки событий» на PHP-сервере в то время, когда должно быть запущено следующее событие.Это время опроса нужно будет уменьшить или увеличить по желанию, так как событие может быть удалено и добавлено в очередь и.Есть какие-нибудь идеи по поводу элегантного способа сделать это? Существует просто много дополнительных затрат на вызов PHP извне (необходимость разбора HTTP-запроса или вызов через CLI), чтобы сделать эту идею осуществимой для моих нужд.

Мой текущий план - написать PHP-демон, который будет запускать событие и взаимодействовать с ним с PHP-сервера с gearman.Демон PHP будет построен на SplMinHeap , так что, надеюсь, производительность не будет плохой.Эта идея оставляет неприятный привкус во рту, и мне было интересно, есть ли у кого идея получше? Идеи немного изменились.Читать Редактировать 2.

РЕДАКТИРОВАТЬ:

Я создаю онлайн-игру, которая развивает игроков по очереди с переменным ограничением времени.Я использую XMPP и BOSH, чтобы позволить мне отправлять сообщения своим клиентам и от них, но я выполнил эту часть, и все работает.Теперь я пытаюсь добавить произвольное событие, которое запускается после игры от клиента, чтобы позволить клиенту (и другим игрокам в игре), что он занял много времени.Я не могу использовать синхронизированный триггер на стороне клиента, потому что это может быть использовано (так как клиент может играть самостоятельно).Надеюсь, что это помогает.

РЕДАКТИРОВАТЬ 2:

Спасибо всем за ваш отзыв.Хотя я думаю, что большинство ваших идей будут работать хорошо в небольших масштабах, у меня есть ощущение, что они не будут очень хорошо масштабироваться (внешний менеджер событий) или не будут обладать точностью, необходимой для этого проекта (CRON).Кроме того, в обоих случаях они являются внешними компонентами, которые могут дать сбой и усложнить и без того сложную систему.

Лично я считаю, что единственное чистое решение, которое соответствует требованиям для этого проектанаписать PHP-демон, который обрабатывает отложенные события.Я начал писать то, что я думаю о first PHP runloop.Он управляет просмотром сокетов и выполнением отложенных событий PHP.Надеюсь, когда я закончу с этим проектом, я смогу опубликовать источник, если кто-то из вас заинтересован в этом.До сих пор в тестировании это было многообещающим решением (никаких проблем с утечкой памяти или нестабильностью).

РЕДАКТИРОВАТЬ 3: Вот ссылка на библиотеку цикла событий PHP под названием LooPHP для тех, ктокому интересно.

TL; Требования DR

  • Вызов (желательно изначально) PHP с задержкой (от секунд до дней)
  • Произвольно обрабатывать создание / обновление / удаление событий (я ожидаю большое количество отмененных вызовов).
  • Обработка высокой загрузки запланированных событий (100-1000 в секунду на сервер)
  • Звонки должны быть в пределах одной секунды от запланированного времени
  • На данный момент я не готов переписать кодовую базу на другой язык (возможно, когда-нибудь я буду)

Ответы [ 15 ]

10 голосов
/ 30 июня 2010

Сделайте, чтобы ваш php-скрипт выполнил вызов exec, чтобы запланировать запуск вашего PHP-скрипта в нужное время, используя команду "at"

exec ("в 22:56 / usr / bin / php myscript.php");

at выполняет команды в указанное время.

со страницы руководства:

At позволяет довольно сложные временные характеристики, расширяя POSIX.2. стандарт. Это принимает времена формы ЧЧ: ММ, чтобы выполнить работу в точное время суток. (Если это время уже прошло, следующий день Предполагается.) Вы также можете указать полночь, полдень или чаепитие (4 часа дня) и Вы можете иметь суффикс времени дня или ночи для запуска в утро или вечер. Вы также можете сказать, в какой день работа будет запущена, указав дату в форме имени месяца с указанием необязательного года, или указание даты в форме ММДДГГ или ММ / ДД / ГГ или ДД.ММ.ГГ. Спецификация Катион даты должен соответствовать спецификации времени суток. Вы также может дать время, как сейчас + подсчитать единицы времени, где единицы времени могут быть минуты, часы, дни или недели, и вы можете указать, чтобы запустить работу сегодня, добавив время к сегодняшнему дню и запустив работу завтра добавив время к завтрашнему дню.

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

7 голосов
/ 25 июня 2010

Я думаю, что решение, основанное только на PHP, будет трудно (почти невозможно) реализовать. Я придумал два решения вашей проблемы.

PHP / Redis решение

Вопрос, заданный Кендаллом:

  • Насколько стабилен redis:

Redis очень стабильный. Разработчик действительно пишет некоторый чистый C-код. Вы должны проверить это на github;). Также много больших сайтов используют Redis. Например, github. У них был действительно интересный блог post , как они сделали github быстрым :). Также superfeedr использует redis . Есть гораздо больше крупных компаний, которые используют Redis;). Я бы посоветовал вам за это погуглить;).

  • Насколько PHP-дружественен redis:

PHP очень удобен для PHP. Многие пользователи пишут PHP-библиотеки для Redis. Протокол действительно прост. Вы можете отладить его с помощью telnet;). Например, для быстрого просмотра predis реализована блокировка.

  • как мне удалить события:

Я думаю, вы должны использовать что-то вроде ZRemCommand .

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

Что я придумал (Псевдокод ....):

processor.php:

<?php
######----processer.php
######You should do something like nohup php processor.php enough times for processors to run event. 
#$key: should be unique, but should also be used by wakeup.php
while(true) {
    $event = blpop($key); #One of the available blocking threads will wakeup and process event
    process($event); #You should write process. This could take some time so this process could not be available
    zrem($key1, $event); #Remove event after processing it. Added this later!!!!!!
}

client.php:

######----client.php
######The user/browser I guess should generate these events.
#$key1: should be unique.
#$millis: when event should run
#$event: just the event to work on.

if ("add event") {
  zadd($key1, $millis, $event);
} else if ("delete event") {
  zremove($key1, $event)
}

#Get event which has to be scheduled first
$first = zrange($key1, 0, 0);

if ($oldfirst <> $first) { #got different first event => notify wakeup.php.
    lpush($key2, $first);
}

$oldfirst = $first;

wakeup.php:

####wakeup.php
#### 1 time do something like nohup php wakeup.php
#http://code.google.com/p/redis/wiki/IntroductionToRedisDataTypes => read sorted set part.
while(true) {
    $first = zrange($key1, 0, 0);
    $event = blpop($key2, $timeoutTillFirstEvent);

    if ($event == nill) {
        #Blockingqueue has timedout which means event should be run by 1 of blocking threads.
        blpop($key2, $first);
    }    
}

Нечто подобное можно было бы написать довольно эффективный планировщик, используя только PHP (хорошо, redis - это C, так что быстро :)), и это тоже будет довольно эффективно :). Я также хотел бы закодировать это решение, так что следите за обновлениями;). Я думаю, что мог бы написать пригодный прототип за день ....

Мое решение Java

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

  1. скачать

    Посетите страницу загрузки github , чтобы загрузить файл jar (со всеми зависимостями).

  2. установить :

    java -jar schedule-broadcaster-1.0-SNAPSHOT-jar-with-dependencies-1277709762.jar

  3. Запуск простых фрагментов PHP

    1. Первый php -f scheduler.php
    2. Далее php -f receiver.php
  4. Вопросы

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

Task Engine TaskQueue

Быстрое решение состоит в том, чтобы использовать очередь задач Google, которая имеет разумную бесплатную квоту. После этого вы должны заплатить за то, что вы используете.

Использование этой модели, задача App Engine API очереди позволяет указывать задачи как HTTP-запросы (как содержимое запрос как его данные, а целевой URL запроса в качестве его кода ссылка). Программно ссылаясь на связанный HTTP-запрос в этом моду иногда называют «паутиной» крюк. "

Важно, что автономный характер API очереди задач позволяет указать веб-крючки раньше времени, без в ожидании их фактического исполнения. Таким образом, приложение может создать много веб-крючки сразу, а затем передать их выкл в App Engine; система будет затем обрабатывать их асинхронно в фон (путем «вызова» HTTP запрос). Эта модель веб-хука позволяет эффективная параллельная обработка - приложение Engine может вызвать несколько задач, или веб-крючки, одновременно.

Подводя итог, API очереди задач позволяет разработчику выполнять работу в фон, асинхронно,разбить эту работу на офлайн крючки. Система вызовет те веб-хуки от имени приложения, планирование для оптимальной производительности возможно выполнение нескольких веб-хуков в параллели. Эта модель гранулированная единицы работы, основанные на HTTP стандарт, позволяет App Engine эффективно выполнять фон обработка таким образом, что работает с любой язык программирования или веб рамки приложения.

4 голосов
/ 25 июня 2010

Это кажется идеальным местом для очереди событий в базе данных.

Сделайте, чтобы ваши созданные пользователем события (вызванные посещением веб-страницы) создали запись в БД, которая содержит инструкции о том, какое действие должно произойти, и отметку времени, когда это должно произойти.You Daemon (либо постоянное приложение, либо запущенное CRON) проверяет в БД события, которые должны были произойти ($TriggerTime <= time()) и которые еще не были помечены как "обработанные".Если вы найдете одно или несколько из этих событий, выполните инструкцию и, наконец, отметьте событие как «обработанное» в БД или просто удалите запись.

Преимущество использования БД для хранения событий (а не того, что находится в ОЗУ приложения) заключается в том, что вы можете восстанавливаться после сбоя без потери данных, вы можете иметь более одного работающего чтенияв одном событии за раз, и вы можете просто изменить событие.

Также есть много людей, которые используют PHP в качестве основного языка сценариев демона на серверах и т. Д. Cron может выполнить сценарий PHP (и подтвердить, что экземпляр этого «приложения» уже запущен), который проверяетОчередь событий время от времени.У вас может быть небольшое приложение, которое умирает после минуты бездействия, а затем перезапускается CRON.Приложение может проверять записи в БД с быстрой частотой по вашему выбору (например, 1 с).Обычно Cron не может выполнять синхронизацию быстрее, чем раз в минуту.

2 голосов
/ 25 июня 2010

Я рекомендую также стратегию очереди, но вам, похоже, не нравится использовать базу данных в качестве очереди.У вас есть инфраструктура XMPP, поэтому используйте ее: используйте pubsub Node и публикуйте свои события на этом узле.При желании Pubsub может быть настроен на постоянное хранение невыполненных элементов.

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

1 голос
/ 04 июля 2010

Вы можете использовать Node.JS, который является управляемым событиями, основанным на JavaScript веб-сервером.Запустите его на секретном внутреннем порту со сценарием, который получает уведомление от сценария PHP и затем планирует действие, которое будет выполнено через xx секунд.Действие в Node.JS может быть таким же простым, как запуск сценария PHP на главном веб-сервере.

0 голосов
/ 03 марта 2015

оформить заказ с помощью redis. может быть полезно для вашей проблемы

https://github.com/chrisboulton/php-resque-scheduler

0 голосов
/ 20 января 2013
  • Сохранение всех задач в базе данных с временем запуска
  • Задание Cron выполняется каждый час
    • Чтение следующих 60 минут заданий
    • Основной цикл
      • Выход, если ничего не делать
      • Микросон до следующего задания
      • Отправка всех заданий во время запуска <= сейчас + 0,5 секунды </li>
0 голосов
/ 11 июня 2011

Существует чисто PHP-решение.Почти то, что сказал Эван в своем ответе.Нагрузка на БД может быть уменьшена (и проблема блокировки), просто введя состояние «Обработка» для событий.Когда сценарий обработки получает события из очереди (БД), они помечаются как «Обработка» и фиксируются.После завершения сценария они помечаются как «Обработанные».Если произошла ошибка или произошел сбой сценария, события «Обработка» должны быть возвращены в исходное состояние.(Это должен был быть комментарий к ответу Эвана, но у меня пока недостаточно репутации)

0 голосов
/ 02 июля 2010

Вот правильный ответ, но он вам может не понравиться.

PHP разработан исключительно для использования в качестве языка запроса-ответа (http), и поэтому не поддерживает то, что вы ищете - это здорово взломать и найти способ обойти, но это будет просто взлом Какое бы «решение» вы ни получили.

Что вам действительно нужно, так это язык, управляемый событиями, который поддерживает xmpp, и для этого вам не нужно смотреть дальше, чем node.js / v8 и поддерживающие библиотеки XMPP - он изначально поддерживает и предназначен именно для того, что вам нужно. Вы также можете пойти по Java-маршруту, но если вы хотите быстро портировать и получить целый ряд новых функций и поддержку того, что вы делаете, то узел - единственный.

Если вы настаиваете на том, чтобы переходить на PHP (как я уже много раз в течение многих лет), то «самым легким» и наиболее эффективным способом сделать это является постоянный демон PHP с очередью событий в базе данных - к сожалению!

0 голосов
/ 02 июля 2010

Как насчет использования любого cron для запуска средства проверки, которое может, например, выполнять вещи из БД.

Или использовать команду "at" linux для планирования выполнения какой-либо команды?

...