Масштабируемая, отложенная обработка PHP

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

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

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

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

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

EDIT 2:

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

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

EDIT 3: Вот ссылка на библиотеку циклов событий PHP под названием LooPHP для тех, кто интересуется.

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

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

Solutions Collecting From Web of "Масштабируемая, отложенная обработка PHP"

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

Решение PHP / Redis

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

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

Редис очень стабилен. Разработчик действительно пишет чистый C-код. Вы должны проверить это на github;). Также много больших сайтов используют redis. Например, github. У них было действительно интересное сообщение в блоге, как они быстро сделали 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 (Okay redis – это C так kickass fast :)), и это было бы довольно эффективно :). Я также хотел бы закодировать это решение, поэтому остался настроенным;). Я думаю, что я мог бы написать полезный прототип через день ….

Мое 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 имеется небольшая документация.

TaskQueue для App Engine

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

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

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

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

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

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

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

со страницы man:

At позволяет довольно сложные спецификации времени, расширяя стандарт POSIX.2. Он принимает время формы HH: MM для выполнения задания в определенное время суток. (Если это время уже прошло, на следующий день предполагается.) Вы также можете указать полуночью, полудню или чаепитие (16:00), и у вас может быть время суток с AM или PM для работы по утрам или вечер. Вы также можете сказать, в какой день будет запущена работа, указав дату в форме месяца-месяца с дополнительным годом или давая дату формы MMDDYY или MM / DD / YY или DD.MM.YY. Спецификация даты должна соответствовать спецификации времени суток. Вы также можете указать время, например: + count time-units, где единицы измерения времени могут составлять минуты, часы, дни или недели, и вы можете сказать, чтобы сегодня запустить работу, суффикс времени с сегодняшнего дня и завтра запустить работу суффикс времени с завтрашним днем.

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

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

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

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

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

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

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

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

Как насчет этого:

http://www.phpjobscheduler.co.uk/

Я не уверен, почему вы пытаетесь избежать cron. Вы можете создать очередь запросов в таблице и запустить cron для проверки текущих заданий.

Есть несколько проблем, в зависимости от ваших точных требований. Например:

  • Насколько точным должен быть вызов?
  • Как долго длится каждый звонок?
  • Какова нормальная и пиковая нагрузка в любой заданный период?

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

У меня много daeon, которые запускают PHP (используя daemontools). При таком подходе вы можете хранить запросы в ядре и выполнять любые временные интервалы, которые вы хотели бы сделать внутренне.

Однако, если точное и релевантное время – то, что вы хотите, вероятно, вам следует вообще отказаться от PHP.

Не может думать ни о чем, что делает все, что вы просили:

  • должен быть очень точным
  • задержка в течение длительных периодов времени
  • способность удалять / изменять время события

Тривиальным способом было бы использовать комбинацию следующих функций:

 set_time_limit(0); ignore_user_abort(true); time_sleep_until(strtotime('next Friday')); // execute code 

Однако, как и @deceze, это, вероятно, не очень хорошая идея, поскольку, если вы настроите высокую задержку, Apache может в конечном итоге убить дочерний процесс (если вы не используете PHP CLI, это упростит). Он также не позволяет вам изменять / удалять событие, если вы не настроите более сложную логику и базу данных для хранения событий. Кроме того, register_shutdown_function() может оказаться полезным, если вы хотите пойти по этой дороге.

Лучшим подходом было бы, по моему мнению, создать работу CRON.

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

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

Как насчет того, чтобы использовать cron для запуска checker, который может выполнять данные из DB, например.

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

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

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

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

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

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

  • Хранить все задачи в базе данных с временем увольнения
  • Задача Cron работает каждый час
    • Прочитайте следующие 60 минут работы
    • Основной цикл
      • Выход, если ничего не делать
      • Microsleep до следующей работы
      • Отправлять все задания во время стрельбы <= теперь + 0,5 секунды

проверьте это с помощью redis. могут быть полезны для вашей проблемы

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

Используйте функцию сна: http://php.net/sleep