Статья однозначно интересная. Но я такие проблемы просто решаю очередями. То есть есть основное приложение, есть консьюмеры очередей. Если нужно сделать какую-то задачу через очередь синхронно ( с блокированием потока приложения), то это делается через RPC. То есть создаётся временная эксклюзивная очередь (читать из неё может только создатель, и живёт она до конца соединения). После этого в какую-то стандартную очередь отправляется сообщение для выполнения конкретной задачи, с пометкой - ответь мне вот в эту временную очередь. Дальше основное приложение уходит в цикл и ждёт ответа (естественно, с ограничением по таймауту). В это время любой свободный консьюмер принимает сообщение, выполняет обработку и отправляет ответ во временную очередь.
Если нужно сделать какую-то большую работу, как с блокировкой, так и без, то можно воспользоваться неким подобием map-reduce. Для примера, возьмём Вашу задачу с экспортом таблицы из БД. Также нужна временная очередь, но уже не эксклюзивная. Для начала я получаю только ИД всех строк в таблице, после этого делю на кусочки по какому-нибудь BATCH_SIZE, скажем 500. И каждый такой кусочек (только ИД) посылаю в очередь. Если нужно сохранять порядок, то посылаю ещё и порядковый номер. В конце посылаю сообщение на обработку всех результатов (в нём указывается сколько было всего этих кусочков). Каждый кусочек обрабатывается консьюмером (как SELECT * FROM table WHERE id IN (:ids) , если нужно сохранять порядок, то добавляем ещё и ORDER BY). и результат пишется куда-то во временное хранилище (ключ-значение). Во временную очередь отправляется имя ключа во временном хранилище. После обработки всех кусочков очередь доходит до обработки всех значений. Но там нужна дополнительная логика. Мы каждый раз смотрим количество сообщений во временной очереди, и если оно меньше ожидаемого (а мы указали общее количество кусочков при отправке этого сообщения), то просто передобавляемся в очередь. Если оно равно ожидаемому, то просто достаём все сообщения из очереди, достаём из временного хранилища результат по ключу и пишем куда-то в постоянное хранилище (будь то файл, БД или что либо ещё).
Если то же самое нужно сделать с блокировкой, то просто можно в основном приложении после отправки читать сообщения из временной очереди и обрабатывать результаты, пока не получим количество результатов, равное изначальному количеству кусочков.
Консьюмеры как раз и будут здесь вашими "демонами", и их желательно перезапускать, потому что память всё равно будет течь :) Обычно это делается просто ограничением по времени работы процесса PHP, но не set_time_limit, а запоминать время начала команды и после обработки каждого сообщения смотреть, прошло ли время N. Таким образом, мы не остановим работу посреди обработки сообщения.
Про гонки Вы всё правильно написали, но я бы добавил, что если изначальное значение поля не важно для результата, то лучше его вообще не читать, а просто обновить. То есть
Конечно, без проблем. Но в данном случае, у меня есть не один тип. У меня ещё есть text, html и xml. И не изменяя тело контроллера, поменяв всего лишь аннотацию с Ajax(«json») на Ajax(«text»), Ajax(«html») или Ajax(«xml»), на выходе получается разный тип контента.
Symfony 2 — это не веб фреймворк, а набор библиотек, на основе которого и был создан Symfony2 Framework. А на основе Symfony 2 даже собираются делать Drupal 8. Насколько я помню, это написано в официальном блоге Фабиена.
основы идеологии: di, container, по-моему, уже давно должны стать основой идеологии любого фреймворка. Порог входа большой, да. Но это ничего не меняет.
Друзья, я не хочу, что бы вы здесь спорили! Я написал эту статью лишь для того, чтобы помочь сообществу. Я очень хочу, что бы Symfony была популярной среди разработчиков. И очень хочу, чтобы разработчики знали как под неё писать. Если кому-то она действительна интересна, то я могу написать ещё статей.
А вообще, меня очень печалит, что на хабре очень мало статей о Symfony 2. Ведь framework действительно стоящий, а большинство статей либо дважды два, либо просто перевод документации. Попытался просто помочь сообществу, надеюсь кому-то это нужно.
Статья однозначно интересная. Но я такие проблемы просто решаю очередями. То есть есть основное приложение, есть консьюмеры очередей. Если нужно сделать какую-то задачу через очередь синхронно ( с блокированием потока приложения), то это делается через RPC. То есть создаётся временная эксклюзивная очередь (читать из неё может только создатель, и живёт она до конца соединения). После этого в какую-то стандартную очередь отправляется сообщение для выполнения конкретной задачи, с пометкой - ответь мне вот в эту временную очередь. Дальше основное приложение уходит в цикл и ждёт ответа (естественно, с ограничением по таймауту). В это время любой свободный консьюмер принимает сообщение, выполняет обработку и отправляет ответ во временную очередь.
Если нужно сделать какую-то большую работу, как с блокировкой, так и без, то можно воспользоваться неким подобием map-reduce. Для примера, возьмём Вашу задачу с экспортом таблицы из БД. Также нужна временная очередь, но уже не эксклюзивная. Для начала я получаю только ИД всех строк в таблице, после этого делю на кусочки по какому-нибудь BATCH_SIZE, скажем 500. И каждый такой кусочек (только ИД) посылаю в очередь. Если нужно сохранять порядок, то посылаю ещё и порядковый номер. В конце посылаю сообщение на обработку всех результатов (в нём указывается сколько было всего этих кусочков). Каждый кусочек обрабатывается консьюмером (как
SELECT * FROM table WHERE id IN (:ids)
, если нужно сохранять порядок, то добавляем ещё иORDER BY
). и результат пишется куда-то во временное хранилище (ключ-значение). Во временную очередь отправляется имя ключа во временном хранилище. После обработки всех кусочков очередь доходит до обработки всех значений. Но там нужна дополнительная логика. Мы каждый раз смотрим количество сообщений во временной очереди, и если оно меньше ожидаемого (а мы указали общее количество кусочков при отправке этого сообщения), то просто передобавляемся в очередь. Если оно равно ожидаемому, то просто достаём все сообщения из очереди, достаём из временного хранилища результат по ключу и пишем куда-то в постоянное хранилище (будь то файл, БД или что либо ещё).Если то же самое нужно сделать с блокировкой, то просто можно в основном приложении после отправки читать сообщения из временной очереди и обрабатывать результаты, пока не получим количество результатов, равное изначальному количеству кусочков.
Консьюмеры как раз и будут здесь вашими "демонами", и их желательно перезапускать, потому что память всё равно будет течь :) Обычно это делается просто ограничением по времени работы процесса PHP, но не
set_time_limit
, а запоминать время начала команды и после обработки каждого сообщения смотреть, прошло ли время N. Таким образом, мы не остановим работу посреди обработки сообщения.Про гонки Вы всё правильно написали, но я бы добавил, что если изначальное значение поля не важно для результата, то лучше его вообще не читать, а просто обновить. То есть
SELECT value FROM table WHERE ... FOR UPDATE;
UPDATE table SET value = :updatedValue WHERE ...;
будет хуже, чем
UPDATE table SET value = value + :diff WHERE ...;