Подтвердить что ты не робот

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

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

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

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

EDIT:

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

ИЗМЕНИТЬ 2:

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

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

ИЗМЕНИТЬ 3: Вот ссылка на библиотеку циклов событий PHP, называемую LooPHP для тех, кто заинтересован.

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

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

Ответ 1

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

Решение PHP/Redis

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

  • Как стабилен redis:

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

  • Как 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-программу, которую вы можете использовать для своей проблемы.

  • скачать

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

  • установить:

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

  • Выполнять простые PHP-фрагменты

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

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

App Engine TaskQueue

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

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

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

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

Ответ 2

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

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

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

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

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

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

Ответ 3

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

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

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

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

Ответ 4

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

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

Ответ 5

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

Ответ 7

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

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

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

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

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

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

Ответ 8

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

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

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

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

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

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

Ответ 9

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

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

Ответ 10

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

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

Ответ 11

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

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

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

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

Ответ 12

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

Ответ 13

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