{{TOC}} {{DOCVER 5.3=c06d6a2352ed8c767633aab9c20f2bf7d880c967 28.01.2017 5:00:51, 5.2=6b0b057ae6de3c88cb29188459e38383c622ec23 8.12.2016 23:00:15, 5.1=cdc24ba7426c5b11eb4d050706bd78c3ea4913cc 19.06.2016 20:08:01, 5.0=5d10040a981deee82c0fde0e8e5d2ffc49eaaecb 8.02.2016 18:09:11}} == Введение == Очереди Laravel предоставляют единое API для различных сервисов очередей, таких как Beanstalk, Amazon SQS, Redis или даже реляционных БД. Очереди позволяют вам отложить выполнение времязатратных задач, таких как отправка e-mail, на более позднее время, таким образом на порядок ускоряя обработку веб-запросов в вашем приложении. Настройки очередей хранятся в файле %%(t)config/queue.php%%. В нём вы найдёте настройки для каждого драйвера очереди, которые поставляются вместе с фреймворком: база данных, ((http://kr.github.com/beanstalkd Beanstalkd)), ((http://iron.io/ IronMQ)) (только для версии 5.1 и ранее), ((http://aws.amazon.com/sqs Amazon SQS)), ((http://redis.io Redis)), %%(t)null%%, а также синхронный драйвер (для локального использования). Драйвер %%(t)null%% просто отменяет задачи очереди, поэтому они никогда не выполнятся. %%(DOCNEW 5.3=c06d6a2352ed8c767633aab9c20f2bf7d880c967 28.01.2017 5:00:51) === Подключения или очереди === Перед изучением очередей Laravel важно понимать различие между "подключениями" и "очередями". В вашем файле настроек %%(t)config/queue.php%% есть параметр %%(t)connections%%, который определяет конкретное подключение к сервису очередей, такому как Amazon SQS, Beanstalk или Redis. Но у любого подключения может быть несколько "очередей", которые можно представить в виде отдельных стеков или наборов задач в очереди. Обратите внимание, в каждом примере настройки подключения в файле %%(t)queue%% есть атрибут %%(t)queue%%. Это та очередь, в которую будут помещаться задачи при отправке по данному подключению. Другими словами, если вы отправляете задачу без явного указания очереди, задача будет помещена в очередь, заданную в атрибуте %%(t)queue%% в настройках подключения: ~%% // Эта задача отправлена в очередь по умолчанию... dispatch(new Job); // Эта задача отправлена в очередь "emails"... dispatch((new Job)->onQueue('emails')); ~%% Для некоторых приложений нет необходимости помещать задачи в несколько очередей, для них достаточно иметь одну простую очередь. Иметь несколько очередей особенно полезно для приложений, в которых необходимо разделение обрабатываемых задач по сегментам или приоритетам. Обработчик очереди Laravel позволяет вам указывать какие задачи имеют больший приоритет. Например, если вы отправите задачу в очередь %%(t)high%%, то можете запустить обработчик, который даст им больший приоритет при выполнении: %%(sh) php artisan queue:work --queue=high,default ~%% %% === Требования для драйверов === **База данных** Для использования драйвера очереди %%(t)database%% вам понадобится таблица в БД для хранения задач. Чтобы генерировать миграцию для создания этой таблицы, выполните Artisan-команду %%(sh)queue:table%%. Когда миграция будет создана, вы сможете мигрировать свою базу данных командой %%(sh)migrate%%: %%(sh) php artisan queue:table php artisan migrate %% %%(DOCNEW 5.3=c06d6a2352ed8c767633aab9c20f2bf7d880c967 28.01.2017 5:00:51) **Redis** Для использования драйвера очереди %%(t)redis%% вам надо настроить подключение к базе данных Redis в файле %%(t)config/database.php%%. Если ваше подключение к очереди Redis использует Redis Cluster, имена ваших очередей должны содержать ((https://redis.io/topics/cluster-spec#keys-hash-tags хештег ключа)). Это необходимо, чтобы убедиться в том, что все ключи Redis для данной очереди помещены в один хеш-слот: %%(conf) 'redis' => [ 'driver' => 'redis', 'connection' => 'default', 'queue' => '{default}', 'retry_after' => 90, ], ~%% %% **Требования других очередей** Упомянутые выше драйвера имеют следующие зависимости: * **Amazon SQS:** %%(t)aws/aws-sdk-php ~3.0%% * **Beanstalkd:** %%(t)pda/pheanstalk ~3.0%% * **IronMQ:** %%(t)iron-io/iron_mq ~2.0|~4.0%% (только для версии 5.1 и ранее) * **Redis:** %%(t)predis/predis ~1.0%% == Создание задач == === Генерирование классов задач === По умолчанию все помещаемые в очередь задачи вашего приложения хранятся в папке %%(t)app/Jobs%% (в версии 5.0 %%(t)App\Commands%%). Если папки %%(t)app/Jobs%% не существует, она будет создана при запуске Artisan-команды %%(sh)make:job%%. Вы можете сгенерировать новую задачу для очереди с помощью Artisan-команды: %%(DOCNEW 5.3=c06d6a2352ed8c767633aab9c20f2bf7d880c967 28.01.2017 5:00:51, 5.2=6b0b057ae6de3c88cb29188459e38383c622ec23 8.12.2016 23:00:15) %%(sh) php artisan make:job SendReminderEmail ~%% %% %%(DOCNEW 5.1=f60f8b3697b3ffe381df4ddb7e2875ffce940643 1.04.2016 17:39:10) %%(sh) php artisan make:job SendReminderEmail --queued ~%% %% %%(DOCNEW 5.0=5d10040a981deee82c0fde0e8e5d2ffc49eaaecb 8.02.2016 18:09:11) %%(sh) php artisan make:command SendEmail --queued ~%% %% Сгенерированный класс будет реализацией интерфейса %%(t)Illuminate\Contracts\Queue\ShouldQueue%%, так Laravel поймёт, что задачу надо поместить в очередь, а не выполнить немедленно. === Структура класса === Классы задач очень просты, обычно они содержат только метод %%handle()%%, который вызывается при обработке задачи в очереди. Для начала давайте посмотрим на пример класса задачи. %%(DOCNEW 5.3=c06d6a2352ed8c767633aab9c20f2bf7d880c967 28.01.2017 5:00:51) В этом примере мы представим, что создаём сервис публикации подкастов, и нам надо обрабатывать загружаемые файлы подкастов перед их публикацией: ~%% podcast = $podcast; } /** * Выполнение задачи. * * @param AudioProcessor $processor * @return void */ public function handle(AudioProcessor $processor) { // Обработка загруженного подкаста... } } ~%% %% %%(DOCNEW 5.2=6b0b057ae6de3c88cb29188459e38383c622ec23 8.12.2016 23:00:15, 5.1=cdc24ba7426c5b11eb4d050706bd78c3ea4913cc 19.06.2016 20:08:01, 5.0=5d10040a981deee82c0fde0e8e5d2ffc49eaaecb 8.02.2016 18:09:11) ~%% user = $user; } /** * Выполнение задачи. * * @param Mailer $mailer * @return void */ public function handle(Mailer $mailer) { $mailer->send('emails.reminder', ['user' => $this->user], function ($m) { // }); $this->user->reminders()->create(...); } } ~%% %% Обратите внимание, в этом примере мы можем передать ((/docs/v5/eloquent модель Eloquent)) напрямую в конструктор задачи. Благодаря используемому в задаче типажу %%(t)SerializesModels%%, модели Eloquent будут изящно сериализованы и десериализованы при выполнении задачи. Если ваша задача принимает модель Eloquent в своём конструкторе, в очередь будет сериализован только идентификатор модели. А когда очередь начнёт обработку задачи, система очередей автоматически запросит полный экземпляр модели из БД. Это всё полностью прозрачно для вашего приложения и помогает избежать проблем, связанных с сериализацией полных экземпляров моделей Eloquent. Метод %%handle()%% вызывается при обработке задачи очередью. Обратите внимание, что мы можем указывать зависимости в методе %%handle()%% задачи. ((/docs/v5/container сервис-контейнер)) Laravel автоматически внедрит эти зависимости. %%(DOCNEW 5.0=5d10040a981deee82c0fde0e8e5d2ffc49eaaecb 8.02.2016 18:09:11) Если вы хотите, чтобы у команды был отдельный класс-обработчик, вам надо добавить ключ %%(sh)--handler%% в команду %%(sh)make:command%%: %%(sh) php artisan make:command SendEmail --queued --handler ~%% %% %%(DOCNEW 5.3=c06d6a2352ed8c767633aab9c20f2bf7d880c967 28.01.2017 5:00:51) .(alert) Бинарные данные, такие как сырое содержимое изображения, должны быть пропущены через функцию %%base64_encode()%% перед отправкой в задачу для очереди. Иначе задача может быть неправильно сериализована в JSON при помещении в очередь. %% %%(DOCNEW 5.2=6b0b057ae6de3c88cb29188459e38383c622ec23 8.12.2016 23:00:15, 5.1=cdc24ba7426c5b11eb4d050706bd78c3ea4913cc 19.06.2016 20:08:01, 5.0=5d10040a981deee82c0fde0e8e5d2ffc49eaaecb 8.02.2016 18:09:11) **Ручной выпуск задач** Если вы хотите "отпустить" задачу вручную, то типаж %%(t)InteractsWithQueue%%, который уже включён в ваш сгенерированный класс задачи, предоставляет доступ к методу задачи %%release()%%. Этот метод принимает один аргумент - число секунд, через которое задача станет снова доступной: ~%% public function handle(Mailer $mailer) { if (condition) { $this->release(10); } } ~%% **Проверка числа попыток запуска** Как уже было сказано, при возникновении исключения при выполнении задачи она будет автоматически возвращена в очередь. Вы можете проверить число сделанных попыток выполнения задачи при помощи метода %%attempts()%%: ~%% public function handle(Mailer $mailer) { if ($this->attempts() > 3) { // } } ~%% %% == Отправка задач в очередь == %%(DOCNEW 5.3=c06d6a2352ed8c767633aab9c20f2bf7d880c967 28.01.2017 5:00:51) Когда вы напишете класс вашей задачи, вы можете отправить её в очередь с помощью вспомогательного метода %%dispatch()%%. Необходимо передать единственный аргумент - экземпляр задачи: ~%% dispatch(new SendReminderEmail($user)); } } ~%% %% %%(DOCNEW 5.2=6b0b057ae6de3c88cb29188459e38383c622ec23 8.12.2016 23:00:15) **Типаж %%(t)DispatchesJobs%%** Разумеется, иногда нужно отправить задачу из другого места вашего приложения - не из маршрута или контроллера. Для этого вы можете включить типаж %%(t)DispatchesJobs%% в любой класс своего приложения, чтобы получить доступ к его различным методам отправки. Например, вот пример класса, использующего этот типаж: ~%% delay(Carbon::now()->addMinutes(10)); dispatch($job); } } ~%% %% %%(DOCNEW 5.2=6b0b057ae6de3c88cb29188459e38383c622ec23 8.12.2016 23:00:15, 5.1=cdc24ba7426c5b11eb4d050706bd78c3ea4913cc 19.06.2016 20:08:01, 5.0=5d10040a981deee82c0fde0e8e5d2ffc49eaaecb 8.02.2016 18:09:11) Например, вы захотите поместить в очередь задачу, которая отправляет пользователю e-mail через 5 минут после регистрации: ~%% delay(60 * 5); $this->dispatch($job); } } ~%% В этом примере мы указали, что задача в очереди должна быть отложена на 5 минут до того, как станет доступной обработчикам. %% .(alert) Сервис Amazon SQS имеет ограничение на задержку не более 15 минут. %%(DOCNEW 5.0=5d10040a981deee82c0fde0e8e5d2ffc49eaaecb 8.02.2016 18:09:11) Вы можете добиться этого, используя метод %%Queue::later()%%: ~%% $date = Carbon::now()->addMinutes(15); Queue::later($date, new SendEmail($message)); ~%% В этом примере мы используем библиотеку для работы со временем ((GIT:../briannesbitt/Carbon Carbon)), чтобы указать необходимое время задержки для задачи. Другой вариант - передать необходимое число секунд для задержки. %% === Настройка очереди и подключения === **Задание очереди для задачи** %%(DOCNEW 5.0=5d10040a981deee82c0fde0e8e5d2ffc49eaaecb 8.02.2016 18:09:11) Вы можете задать очередь, в которую следует посылать задачу: ~%% Queue::pushOn('emails', new SendEmail($message)); ~%% %% Помещая задачи в разные очереди, вы можете разделять их по категориям, а также задавать приоритеты по количеству обработчиков разных очередей. Это не касается различных "подключений" очередей, определённых в файле настроек очереди, а только конкретных очередей в рамках одного подключения. Чтобы указать очередь используйте метод %%onQueue()%% на экземпляре задачи: %%(DOCNEW 5.3=c06d6a2352ed8c767633aab9c20f2bf7d880c967 28.01.2017 5:00:51) ~%% onQueue('processing'); dispatch($job); } } ~%% %% %%(DOCNEW 5.2=6b0b057ae6de3c88cb29188459e38383c622ec23 8.12.2016 23:00:15, 5.1=cdc24ba7426c5b11eb4d050706bd78c3ea4913cc 19.06.2016 20:08:01, 5.0=5d10040a981deee82c0fde0e8e5d2ffc49eaaecb 8.02.2016 18:09:11) ~%% onQueue('emails'); $this->dispatch($job); } } ~%% %% %%(DOCNEW 5.0=5d10040a981deee82c0fde0e8e5d2ffc49eaaecb 8.02.2016 18:09:11) **Передача одинаковых данных в несколько задач** Если вам надо передать одинаковые данные в несколько задач в очереди, вы можете использовать метод %%Queue::bulk()%%: ~%% Queue::bulk([new SendEmail($message), new AnotherCommand]); ~%% %% %%(DOCNEW 5.3=c06d6a2352ed8c767633aab9c20f2bf7d880c967 28.01.2017 5:00:51, 5.2=6b0b057ae6de3c88cb29188459e38383c622ec23 8.12.2016 23:00:15) **Указание подключения к очереди для задачи** Если вы работаете с несколькими подключениями к очередям, то можете указать, в какое из них надо поместить задачу. Для этого служит метод %%onConnection()%% на экземпляре задачи: %% %%(DOCNEW 5.3=c06d6a2352ed8c767633aab9c20f2bf7d880c967 28.01.2017 5:00:51) ~%% onConnection('sqs'); dispatch($job); } } ~%% %% %%(DOCNEW 5.2=6b0b057ae6de3c88cb29188459e38383c622ec23 8.12.2016 23:00:15) ~%% onConnection('alternate'); $this->dispatch($job); } } ~%% %% %%(DOCNEW 5.3=c06d6a2352ed8c767633aab9c20f2bf7d880c967 28.01.2017 5:00:51, 5.2=6b0b057ae6de3c88cb29188459e38383c622ec23 8.12.2016 23:00:15) Само собой, вы можете сцепить методы %%onConnection()%% и %%onQueue()%%, чтобы указать подключение и очередь для задачи: ~%% $job = (new ProcessPodcast($podcast)) ->onConnection('sqs') ->onQueue('processing'); ~%% %% === Обработка ошибок === Если во время выполнения задачи возникло исключение, она будет автоматически возвращена в очередь, и можно будет снова попробовать выполнить её. Попытки будут повторяться до тех пор, пока не будет достигнуто заданное максимальное число для вашего приложения. Это число определяется параметром %%(sh)--tries%%, используемом в Artisan-команде %%(sh)queue:work%%. Подробнее о запуске обработчика очереди ((docs/v5/queues#worker читайте ниже)). %%(DOCNEW 5.3=c06d6a2352ed8c767633aab9c20f2bf7d880c967 28.01.2017 5:00:51) == ((#worker)) Запуск обработчика очереди == Laravel содержит обработчик очереди, который обрабатывает новые задачи при поступлении в очередь. Вы можете запустить его Artisan-командой %%(sh)queue:work%%. Запомните, команда %%(sh)queue:work%% будет выполняться, пока вы не остановите её вручную или закроете свой терминал: %%(sh) php artisan queue:work ~%% .(alert) Чтобы процесс %%(sh)queue:work%% выполнялся в фоне постоянно, используйте монитор процессов, такой как ((#supervisor Supervisor)), для проверки, что обработчик очереди не остановился. Запомните, обработчики очереди - долгоживущие процессы, и они хранят в памяти состояние загруженного приложения. Поэтому они не заметят изменения в вашем коде, сделанные после их запуска. Поэтому в процессе развёртывания не забудьте ((#развёртывание перезапустить обработчиков очереди)). **Указание подключения и очереди** Также вы можете указать, какое подключение должен использовать обработчик очереди. Имя подключения передаётся команде %%(sh)work%% и должно соответствовать одному из подключений, определённых в файле %%(t)config/queue.php%%: %%(sh) php artisan queue:work redis ~%% Можно ещё точнее настроить обработчика очереди, указав для него конкретные очереди в данном подключении. Например, если все ваши email-сообщения обрабатываются в очереди %%(t)emails%% в подключении %%(t)redis%%, вы можете выполнить такую команду для запуска обработчика только для этой очереди: %%(sh) php artisan queue:work redis --queue=emails ~%% **Освобождение ресурсов** Демоны-обработчики очереди не "перезагружают" фреймворк перед обработкой каждой задачи. Поэтому вам надо освобождать все тяжёлые ресурсы после завершения каждой задачи. Например, если вы выполняете обработку изображения с помощью библиотеки GD, то после завершения обработки вам надо освободить память с помощью %%imagedestroy%%. === Приоритеты очередей === Иногда бывает необходимо организовать обработку очередей по их приоритетам. Например, в файле %%(t)config/queue.php%% вы можете задать очередь %%(t)low%% как очередь по умолчанию для подключения %%(t)redis%%. При этом при необходимости вы можете помещать задачу в очередь %%(t)high%% вот так: ~%% dispatch((new Job)->onQueue('high')); ~%% Чтобы запустить обработчика, который сначала проверит, что обработаны все задачи из очереди %%(t)high%%, и только потом продолжит обрабатывать задачи из очереди %%(t)low%%, передайте в команду %%(sh)%% список очередей через запятую: %%(sh) php artisan queue:work --queue=high,low ~%% === ((#развёртывание)) Обработчики очереди и развёртывание=== Поскольку обработчики очереди - долгоживущие процессы, они не подхватят изменения в вашем коде, пока не будут перезапущены. Поэтому простейший способ развернуть приложение с обработчиками очереди - перезапустить обработчиков во время процесса развёртывания. Вы можете мягко перезапустить всех своих обработчиков командой %%(sh)queue:restart%%: %%(sh) php artisan queue:restart ~%% Эта команда заставит обработчиков очереди мягко "умереть" после завершения ими текущей задачи, поэтому ни одна существующая задаче не будет потеряна. Поскольку обработчики очереди умрут при выполнении команды %%(sh)queue:restart%%, вам надо запустить менеджер процессов, такой как ((#supervisor Supervisor)), для автоматического перезапуска обработчиков очереди. === Окончание срока задачи и таймауты === **Окончание срока задачи** В файле %%(t)config/queue.php%% для каждого подключения определён параметр %%(t)retry_after%%. Этот параметр указывает, сколько секунд подключение должно ждать, прежде чем заново приступить к обрабатываемой задаче. Например, если значение %%(t)retry_after%% равно %%(t)90%%, то задача будет возвращена назад в очередь, если пробудет в обработке 90 секунд и не будет удалена. Обычно для %%(t)retry_after%% лучше задать значение равное максимальному разумному количеству секунд, которое потребуется задачам для выполнения. .(alert) Единственное подключение, у которого нет значения %%(t)retry_after%%, - Amazon SQS. SQS пробует выполнить задачу заново исходя из ((https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/AboutVT.html стандартного таймаута видимости)), который настраивается в консоли AWS. **Таймауты обработчиков** Artisan-команда %%(sh)queue:work%% имеет параметр %%(sh)--timeout%%. Этот параметр указывает, сколько должен ждать главный процесс обработки очередей Laravel, прежде чем уничтожить дочернего обработчика очереди, выполняющего задачу. Дочерний процесс обработки очереди может "зависнуть" по разным причинам, таким как внешний HTTP-вызов, который не отвечает. Параметр %%(sh)--timeout%% удаляет зависнувшие процессы, превысившие указанный лимит времени: %%(sh) php artisan queue:work --timeout=60 ~%% Параметр %%(t)retry_after%% в настройках и параметр командной строки %%(sh)--timeout%% разные, но работают совместно для проверки, что задачи не потерялись, и что задачи выполняются успешно только один раз. .(alert) Значение %%(sh)--timeout%% должно быть всегда по крайней мере на несколько секунд меньше значения %%(t)retry_after%% в настройках. Таким образом обработчик, выполняющий данную задачу, будет всегда убиваться до повторной попытки выполнения задачи. Если ваш параметр %%(sh)--timeout%% имеет значение больше значения %%(t)retry_after%%, ваши задачи могут выполниться дважды. **Длительность паузы обработчика** Пока в очереди доступны задачи, обработчик будет выполнять их без задержки между ними. С помощью параметра %%(sh)sleep%% можно задать время, на которое будет "засыпать" обработчик, если нет новых задач: %%(sh) php artisan queue:work --sleep=3 ~%% %% %%(DOCNEW 5.1=cdc24ba7426c5b11eb4d050706bd78c3ea4913cc 19.06.2016 20:08:01, 5.0=5d10040a981deee82c0fde0e8e5d2ffc49eaaecb 8.02.2016 18:09:11) === Получение задач из запросов === Очень часто переменные HTTP-запросов переносятся в задачи. Поэтому вместо того, чтобы заставлять вас делать это вручную для каждого запроса, Laravel предоставляет несколько вспомогательных методов. Давайте посмотрим на метод %%dispatchFrom()%% типажа %%(t)DispatchesJobs%%. По умолчанию этот типаж включён в базовый класс контроллера Laravel: ~%% dispatchFrom('App\Jobs\ProcessOrder', $request); } } ~%% Этот метод проверит конструктор класса данной задачи и извлечёт переменные из HTTP-запроса (или любого другого объекта %%(t)ArrayAccess%%) для заполнения необходимых параметров конструктора задачи. Поэтому, если класс нашей задачи принимает в конструкторе переменную %%(t)productId%%, то шина задач попытается получить параметр %%(t)productId%% из HTTP-запроса. Вы также можете передать массив третьим аргументом метода %%dispatchFrom()%%. Этот массив будет использован для заполнения тех параметров, которых нет в запросе: ~%% $this->dispatchFrom('App\Jobs\ProcessOrder', $request, [ 'taxPercentage' => 20, ]); ~%% %% %%(DOCNEW 5.0=5d10040a981deee82c0fde0e8e5d2ffc49eaaecb 8.02.2016 18:09:11) === Добавление замыканий в очередь === Вы можете помещать в очередь и функции-замыкания. Это очень удобно для простых, быстрых задач, выполняющихся в очереди. **Добавление замыкания в очередь** ~%% Queue::push(function($job) use ($id) { Account::delete($id); $job->delete(); }); ~%% .(alert) Вместо того, чтобы делать объекты доступными для замыканий в очереди через директиву %%use%%, передавайте первичные ключи и повторно извлекайте связанные модели из вашей задачи в очереди. Это часто позволяет избежать неожиданного поведения при сериализации. При использовании ((#push push-очередей)) //Iron.io// будьте особенно внимательны при добавлении замыканий. Конечная точка выполнения, получающая ваше сообщение, должна проверить входящую последовательность-ключ, чтобы удостовериться, что запрос действительно исходит от //Iron.io//. Например, ваша конечная push-точка может иметь адрес вида %%(t)https://yourapp.com/queue/receive?token=SecretToken%% - где значение **token** можно проверять перед собственно обработкой задачи. %% == ((#supervisor)) Настройка Supervisor == **Установка Supervisor** Supervisor - монитор процессов для ОС Linux, он автоматически перезапустит ваш процесс %%(sh)queue:work%%, если он остановится. Для установки Supervisor в Ubuntu используйте такую команду: %%(sh) sudo apt-get install supervisor %% .(alarm) Если самостоятельная настройка Supervisor кажется вам слишком сложной, вы можете использовать ((https://forge.laravel.com Laravel Forge)), который автоматически установит и настроит Supervisor для ваших проектов Laravel. **Настройка Supervisor** Файлы настроек Supervisor обычно находятся в папке %%(t)/etc/supervisor/conf.d%%. Там вы можете создать любое количество файлов с настройками, по которым Supervisor поймёт, как отслеживать ваши процессы. Например, давайте создадим файл %%(t)laravel-worker.conf%%, который запускает и наблюдает за процессом %%(t)queue:work%%: %%(conf) [program:laravel-worker] process_name=%(program_name)s_%(process_num)02d command=php /home/forge/app.com/artisan queue:work sqs --sleep=3 --tries=3 autostart=true autorestart=true user=forge numprocs=8 redirect_stderr=true stdout_logfile=/home/forge/app.com/worker.log %% В этом примере %%(t)numprocs%% указывает, что Supervisor должен запустить 8 процессов %%(t)queue:work%% и наблюдать за ними, автоматически перезапуская их при их остановках. Само собой, вам надо изменить часть %%(t)queue:work sqs%% директивы %%(t)command%% в соответствии с вашим подключением очереди. Подробнее о Supervisor читайте в ((http://supervisord.org/index.html его документации)). %%(DOCNEW 5.2=6b0b057ae6de3c88cb29188459e38383c622ec23 8.12.2016 23:00:15, 5.1=cdc24ba7426c5b11eb4d050706bd78c3ea4913cc 19.06.2016 20:08:01, 5.0=5d10040a981deee82c0fde0e8e5d2ffc49eaaecb 8.02.2016 18:09:11) **Запуск Supervisor** После создания файла настроек вы можете обновить конфигурацию Supervisor и запустить процесс при помощи следующих команд: %%(sh) sudo supervisorctl reread sudo supervisorctl update sudo supervisorctl start laravel-worker:* ~%% %% == Проваленные задачи == Не всегда всё идёт по плану, иногда ваши задачи в очереди будут заканчиваться ошибкой. Не волнуйтесь, такое с каждым случается! В Laravel есть удобный способ указать максимальное количество попыток выполнения задачи. После превышения этого количества попыток задача будет добавлена в таблицу %%(t)failed_jobs%%. Для создания миграции для таблицы %%(t)failed_jobs%% можно использовать команду %%(sh)queue:failed-table%%: %%(sh) php artisan queue:failed-table php artisan migrate %% При запуске вашего ((/docs/v5/queues#worker обработчика очереди)) нужно указать максимальное количество попыток выполнения задачи с помощью параметра %%(sh)--tries%% команды %%(sh)queue:work%%. Если не задать этот параметр, то попытки выполнения задачи будут бесконечны: %%(sh) php artisan queue:work redis --tries=3 %% %%(DOCNEW 5.3=c06d6a2352ed8c767633aab9c20f2bf7d880c967 28.01.2017 5:00:51) === Уборка после проваленных задач === Вы можете определить метод %%failed()%% прямо в классе задачи, это позволит вам выполнять уборку после данной конкретной задачи при возникновении ошибки. Это идеальное место для отправки предупреждения вашим пользователям или для отмены всех действий, выполненных задачей. Исключение %%Exception%%, приведшее к провалу задачи, будет передано в метод %%failed()%%: ~%% podcast = $podcast; } /** * Выполнить задачу. * * @param AudioProcessor $processor * @return void */ public function handle(AudioProcessor $processor) { // Обработка загруженного подкаста... } /** * Ошибка выполнения задачи. * * @param Exception $exception * @return void */ public function failed(Exception $exception) { // Отправить пользователю уведомление об ошибке, и т.п. ... } } ~%% %% === События проваленных задач === Если вы хотите зарегистрировать событие, которое будет вызываться при ошибке выполнения задачи, используйте метод %%Queue::failing()%%. Это событие - отличная возможность оповестить вашу команду через email или ((http://www.hipchat.com HipChat)). Например, мы можем прикрепить обратный вызов к данному событию из %%(t)AppServiceProvider%%, который включён в Laravel: %% connectionName // $event->job // $event->exception //для версии 5.1 и ранее: //Queue::failing(function ($connection, $job, $data) { // // Оповещение команды о проваленной задаче... }); } /** * Регистрация сервис-провайдера. * * @return void */ public function register() { // } } %% %%(DOCNEW 5.2=6b0b057ae6de3c88cb29188459e38383c622ec23 8.12.2016 23:00:15, 5.1=cdc24ba7426c5b11eb4d050706bd78c3ea4913cc 19.06.2016 20:08:01, 5.0=5d10040a981deee82c0fde0e8e5d2ffc49eaaecb 8.02.2016 18:09:11) **Метод %%failed()%% класса задачи** Для более точного контроля, вы можете определить метод %%failed()%% непосредственно в классе задачи, это позволит выполнять специфичное для задачи действие при возникновении ошибки: ~%% connectionName // $event->job // $event->job->payload() }); Queue::after(function (JobProcessed $event) { // $event->connectionName // $event->job // $event->job->payload() }); } /** * Регистрация сервис-провайдера. * * @return void */ public function register() { // } } ~%% С помощью метода %%looping()%% ((//docs/v5/facades фасада)) %%(t)Queue%% вы можете указать обратные вызовы, которые будут выполнены перед тем, как обработчик попытается получить задачу из очереди. Например, вы можете зарегистрировать замыкание для отката всех транзакций, которые были оставлены открытыми предыдущей проваленной задачей: ~%% Queue::looping(function () { while (DB::transactionLevel() > 0) { DB::rollBack(); } }); ~%% %% %%(DOCNEW 5.2=6b0b057ae6de3c88cb29188459e38383c622ec23 8.12.2016 23:00:15, 5.1=cdc24ba7426c5b11eb4d050706bd78c3ea4913cc 19.06.2016 20:08:01, 5.0=5d10040a981deee82c0fde0e8e5d2ffc49eaaecb 8.02.2016 18:09:11) **События выполнения задачи** Методы %%Queue::before()%% и %%Queue::after()%% позволяют зарегистрировать обратный вызов, который будет выполнен до выполнения задачи или после успешного выполнения задачи. Обратные вызовы - отличная возможность для выполнения дополнительного журналирования, помещения в очередь последующей задачи, или увеличения статистики для панели управления. Например, мы можем прикрепить обратный вызов к этому событию из %%(t)AppServiceProvider%%, который включён в Laravel: ~%% connectionName // $event->job // $event->data //для версии 5.1 и ранее: //Queue::after(function ($connection, $job, $data) { // // }); } /** * Регистрация сервис-провайдера. * * @return void */ public function register() { // } } ~%% == ((#listener)) Запуск слушателя очереди == **Запуск слушателя очереди** Laravel включает в себя Artisan-команду, которая будет выполнять новые задачи по мере их поступления. Вы можете запустить слушателя командой %%(sh)queue:listen%%: %%(sh) php artisan queue:listen ~%% %% %%(DOCNEW 5.2=6b0b057ae6de3c88cb29188459e38383c622ec23 8.12.2016 23:00:15) Вы также можете указать, какое именно соединение должно прослушиваться: %%(sh) php artisan queue:listen connection-name ~%% %% %%(DOCNEW 5.1=cdc24ba7426c5b11eb4d050706bd78c3ea4913cc 19.06.2016 20:08:01, 5.0=5d10040a981deee82c0fde0e8e5d2ffc49eaaecb 8.02.2016 18:09:11) %%(sh) php artisan queue:listen connection ~%% %% %%(DOCNEW 5.2=6b0b057ae6de3c88cb29188459e38383c622ec23 8.12.2016 23:00:15, 5.1=cdc24ba7426c5b11eb4d050706bd78c3ea4913cc 19.06.2016 20:08:01, 5.0=5d10040a981deee82c0fde0e8e5d2ffc49eaaecb 8.02.2016 18:09:11) Заметьте, что когда это задание запущено, оно будет продолжать работать, пока вы не остановите его вручную. Вы можете использовать монитор процессов, такой как ((http://supervisord.org/ Supervisor)), чтобы удостовериться, что задание продолжает работать. **Приоритеты очереди** Вы можете передать команде %%(sh)listen%% список подключений к очереди через запятую, чтобы задать приоритеты для очереди: %%(sh) php artisan queue:listen --queue=high,low ~%% В этом примере задачи из подключения **high-connection** всегда будут обрабатывать перед задачами из **low-connection**. **Указание времени на выполнение задачи** Кроме этого вы можете указать число секунд, в течение которых будет выполняться каждая задача: %%(sh) php artisan queue:listen --timeout=60 ~%% **Указание перерыва между задачами** Также вы можете задать число секунд ожидания перед проверкой наличия новых задач в очереди: %%(sh) php artisan queue:listen --sleep=5 ~%% Обратите внимание, что очередь "засыпает" только тогда, когда в ней нет задач. Если задачи есть, очередь будет продолжать обрабатывать их без перерыва. %% %%(DOCNEW 5.2=6b0b057ae6de3c88cb29188459e38383c622ec23 8.12.2016 23:00:15, 5.0=5d10040a981deee82c0fde0e8e5d2ffc49eaaecb 8.02.2016 18:09:11) **Обработка только первой задачи в очереди** Для обработки только первой задачи можно использовать команду %%(sh)queue:work%%: %%(sh) php artisan queue:work ~%% %% %%(DOCNEW 5.2=6b0b057ae6de3c88cb29188459e38383c622ec23 8.12.2016 23:00:15, 5.1=cdc24ba7426c5b11eb4d050706bd78c3ea4913cc 19.06.2016 20:08:01, 5.0=5d10040a981deee82c0fde0e8e5d2ffc49eaaecb 8.02.2016 18:09:11) === Демон-обработчик очереди === Команда %%(sh)queue:work%% также имеет опцию %%(sh)--daemon%% для того, чтобы обработчик принудительно продолжал обрабатывать задачи без необходимости повторной загрузки фреймворка. Это приводит к значительному снижению использования CPU по сравнению с командой %%(sh)queue:listen%%. Для запуска обработчика очереди в режиме демона используйте флаг %%(sh)--daemon%%: %% %%(DOCNEW 5.2=6b0b057ae6de3c88cb29188459e38383c622ec23 8.12.2016 23:00:15) %%(sh) php artisan queue:work connection-name --daemon php artisan queue:work connection-name --daemon --sleep=3 php artisan queue:work connection-name --daemon --sleep=3 --tries=3 ~%% %% %%(DOCNEW 5.1=cdc24ba7426c5b11eb4d050706bd78c3ea4913cc 19.06.2016 20:08:01, 5.0=5d10040a981deee82c0fde0e8e5d2ffc49eaaecb 8.02.2016 18:09:11) %%(sh) php artisan queue:work connection --daemon php artisan queue:work connection --daemon --sleep=3 php artisan queue:work connection --daemon --sleep=3 --tries=3 ~%% %% %%(DOCNEW 5.2=6b0b057ae6de3c88cb29188459e38383c622ec23 8.12.2016 23:00:15, 5.1=cdc24ba7426c5b11eb4d050706bd78c3ea4913cc 19.06.2016 20:08:01, 5.0=5d10040a981deee82c0fde0e8e5d2ffc49eaaecb 8.02.2016 18:09:11) Как видите, команда %%(sh)queue:work%% поддерживает те же самые опции, что и команда %%(sh)queue:listen%%. Для просмотра всех доступных опций используйте команду %%(sh)php artisan help queue:work%%. **Написание кода для демонов-обработчиков очереди** Демоны-обработчики очереди не перезапускают фреймворк перед обработкой каждой задачи. Поэтому нужно быть аккуратным при освобождении любых "тяжёлых" ресурсов до того, как закончится выполнение вашей задачи. Например, если вы обрабатываете изображение с помощью библиотеки GD, то после завершения обработки вам необходимо освобождать память с помощью %%imagedestroy()%%. %% %%(DOCNEW 5.1=cdc24ba7426c5b11eb4d050706bd78c3ea4913cc 19.06.2016 20:08:01, 5.0=5d10040a981deee82c0fde0e8e5d2ffc49eaaecb 8.02.2016 18:09:11) Также ваше соединение с БД может быть потеряно, когда оно используется демоном долгое время. Вы можете использовать метод %%DB::reconnect()%%, для обеспечения "свежего" соединения. %% %%(DOCNEW 5.2=6b0b057ae6de3c88cb29188459e38383c622ec23 8.12.2016 23:00:15, 5.1=cdc24ba7426c5b11eb4d050706bd78c3ea4913cc 19.06.2016 20:08:01, 5.0=5d10040a981deee82c0fde0e8e5d2ffc49eaaecb 8.02.2016 18:09:11) === Развёртывание с помощью демонов-обработчиков очереди === Поскольку демоны-обработчики очереди - длительные процессы, они не подхватывают изменения в коде без перезапуска. Поэтому простейший способ развернуть приложение, используя обработчики очереди, - перезапустить их во время выполнения скрипта развёртывания. Вы можете изящно перезапустить все обработчики, включив следующую команду в ваш скрипт для развёртывания: %%(sh) php artisan queue:restart ~%% %% %%(DOCNEW 5.2=6b0b057ae6de3c88cb29188459e38383c622ec23 8.12.2016 23:00:15) Эта команда сообщит всем обработчикам очереди о необходимости "умереть" после завершения обработки их текущих задач, поэтому текущие задачи не пропадут. Не забывайте, что обработчики очереди умрут при выполнении команды %%(sh)queue:restart%%, поэтому необходимо запустить менеджер процессов, такой как Supervisor, который автоматически перезапустит обработчики задач. %% %%(DOCNEW 5.1=cdc24ba7426c5b11eb4d050706bd78c3ea4913cc 19.06.2016 20:08:01, 5.0=5d10040a981deee82c0fde0e8e5d2ffc49eaaecb 8.02.2016 18:09:11) Эта команда сообщит всем обработчикам очереди о необходимости перезапуска после завершения обработки их текущих задач, поэтому текущие задачи не пропадут. %% %%(DOCNEW 5.2=6b0b057ae6de3c88cb29188459e38383c622ec23 8.12.2016 23:00:15, 5.1=cdc24ba7426c5b11eb4d050706bd78c3ea4913cc 19.06.2016 20:08:01, 5.0=5d10040a981deee82c0fde0e8e5d2ffc49eaaecb 8.02.2016 18:09:11) .(alert) Эта команда полагается на систему кэша для планирования перезапуска. По умолчанию APCu не работает для терминальных задач. Если вы используете APCu, добавьте %%(conf)apc.enable_cli=1%% в свою конфигурацию для APCu. %% %%(DOCNEW 5.0=5d10040a981deee82c0fde0e8e5d2ffc49eaaecb 8.02.2016 18:09:11) == ((#push)) Push-очереди == Push-очереди дают вам доступ ко всем мощным возможностям, предоставляемым подсистемой очередей Laravel 5, без запуска демонов и фоновых слушателей. На текущий момент push-очереди поддерживает только драйвер //Iron.io//. Перед тем, как начать, создайте аккаунт и впишите его данные в %%(t)config/queue.php%%. **Регистрация подписчика push-очереди** После этого вы можете использовать ((docs/v5/artisan Artisan))-команду %%(sh)queue:subscribe%% для регистрации URL конечной точки, которая будет получать добавляемые в очередь задачи: %%(sh) php artisan queue:subscribe queue_name queue/receive php artisan queue:subscribe queue_name http://foo.com/queue/receive ~%% Теперь, когда вы войдёте в ваш профиль //Iron.io//, то увидите новую push-очередь и URL её подписки. Вы можете подписать любое число URL на одну очередь. Дальше создайте маршрут для вашей конечной точки %%(t)queue/receive%%, и пусть он возвращает результат вызова метода %%Queue::marshal()%%: ~%% Route::post('queue/receive', function() { return Queue::marshal(); }); ~%% Этот метод позаботится о вызове нужного класса-обработчика задачи. Для помещения задач в push-очередь просто используйте всё тот же метод %%Queue::push()%%, который работает и для обычных очередей. %%