Очереди

Настройка

В Laravel компонент Queue предоставляет единое API для различных сервисов очередей. Очереди позволяют вам отложить выполнение времязатратной задачи, такой как отправка e-mail, на более позднее время, таким образом на порядок ускоряя загрузку (генерацию) страницы.

Настройки очередей хранятся в файле config/queue.php. В нём вы найдёте настройки для драйверов-связей, которые поставляются вместе с фреймворком: database - очередь, построенная на таблице в БД, Beanstalkd, IronMQ, Amazon SQS, sync - синхронный драйвер для локального использования и null - запрет использования очередей.

Создание таблицы очереди

Если вы используете драйвер database, то вам нужно создать таблицу, в которой будет организована очередь:

php artisan queue:table

Установка пакетов

Упомянутые выше драйвера имеют следующие зависимости:

  • Amazon SQS: aws/aws-sdk-php
  • Beanstalkd: pda/pheanstalk ~3.0
  • IronMQ: iron-io/iron_mq
  • Redis: predis/predis ~1.0

Использование очередей

Добавление новой задачи в очередь

В очередь в качестве задач (jobs) могут быть добавлены так называемые команды, которые находятся в App\Commands. Вы можете создать такую команду следующим образом:

php artisan make:command SendEmail --queued

Чтобы добавить команду в очередь, воспользуйтесь методом push:

Queue::push(new SendEmail($message));

Примечание: В этом примере мы используем фасад Queue для отправки задачи в очередь. Однако, лучшим способом было бы воспользоваться командной шиной и запустить команду, которая настроена во время своей работы уходить в очередь и исполняться там. Дальше по тексту мы продолжим использовать фасад Queue, однако настоятельно рекомендуем ознакомиться с командной шиной, так как этот концепт позволяет создавать команды, которые можно запускать универсально - и синхронно и в фоне, через очереди.

По умолчанию make:command создает "self-handling" команду, то есть содержащую метод и коструктор класса-команды и метод handle, который исполняет команду. Вы можете передать в этот метод необходимые для работы классы в виде аргументов:

public function handle(UserRepository $users)
{
    //
}

Если же вы хотите разделить команду на два класса, т.е. выделить выполнение в отдельный класс, то добавьте флаг --handler при создании:

php artisan make:command SendEmail --queued --handler

Хэндлер, т.е. класс с методом hanlde будет помещён в App\Handlers\Commands.

Добавление задачи в определенную очередь

У приложения может быть несколько очередей. Можно поместить команду в определенную очередь:

Queue::pushOn('emails', new SendEmail($message));

Передача данных нескольким задачам сразу

Если вам надо передать одни и те же данные нескольким задачам в очереди, вы можете использовать метод Queue::bulk:

Queue::bulk(array(new SendEmail($message), new AnotherCommand));

Отложенное выполнение задачи

Иногда вам нужно, чтобы задача начала исполняться не сразу после занесения её в очередь, а спустя какое-то время. Например, выслать пользователю письмо спустя 15 минут после регистрации. Для этого существует метод Queue::later:

$date = Carbon::now()->addMinutes(15);

Queue::later($date, new SendEmail($message));

Здесь для задания временного периода используется библиотека для работы с временем и датой Carbon, но $date может быть и просто целым числом секунд.

Примечание: У Amazon SQS есть ограничение - максимальная пауза отложенного запуска составляет 900 секунд (15 минут).

Очереди и модели Eloquent

Если ваша команда, которая отправляется в очередь, принимает модель Eloquent в своём конструкторе, в очередь будет передан только её ID. Когда команда выполняется обработчиком очереди (вызывается метод handle), фреймворк автоматически загружает из базы данных экземпляр модели с данным ID. Это происходит полностью прозрачно для приложения.

Удаление выполненной задачи

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

Вы можете вручную удалить команду из очереди и вернуть её обратно в очередь. Для этого вам надо добавить в команду трейт Illuminate\Queue\InteractsWithQueue, который предоставляет методы delete и release.

public function handle(SendEmail $command)
{
    if (true)
    {
        $this->release(30);
    }
}

Параметр в методе release - число секунд, через которое данная команда должна вернуться в очередь.

Помещение команды обратно в очередь

Если во время выполнения команды было брошено непойманное исключение, то команда автоматически помещается обратно в очередь. Так происходит до тех пор, пока не превысится максимальное число попыток, заданное в параметре --tries вашего слушателя очереди.

Получение числа попыток запуска

Вы можете узнать, сколько попыток перезапуска команды уже было:

if ($this->attempts() > 3)
{
    //
}

Примечание: Вы должны включить в класс трейт Illuminate\Queue\InteractsWithQueue чтобы использовать этот метод.

Добавление функций-замыканий в очередь

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

Добавить функцию-замыкание в очередь

Queue::push(function($job) use ($id)
{
    Account::delete($id);

    $job->delete();
});

Примечание: Старайтесь не передавать модели в конструкции use(). Вместо этого передавайте ID объекта, и получайте его из БД в функции-замыкании.

При использовании push-очередей Iron.io, будьте особенно внимательны при добавлении замыканий. Конечная точка выполнения, получающая ваше сообщение из очереди, должна проверить входящую последовательность-ключ, чтобы удостовериться, что запрос действительно исходит от Iron.io. Например, ваша конечная push-точка может иметь адрес вида https://yourapp.com/queue/receive?token=SecretToken где значение token можно проверять перед собственно обработкой задачи.

Обработчик очереди

Задачи, помещенные в очередь должен кто-то исполнять. Laravel включает в себя Artisan-задачу, которая раз в несколько секунд опрашивает очередь и, если в очереди есть задача, запускает в отдельном процессе рабочего (worker), который выполняет её. Задачи запускаются не параллельно, а последовательно. Вы можете запустить её командой queue:listen:

Запуск сервера выполнения задач

php artisan queue:listen

Вы также можете указать, какое именно соединение должно прослушиваться:

php artisan queue:listen connection

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

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

php artisan queue:listen --queue=high,low

Задачи из high будут всегда выполняться раньше задач из low.

Указание числа секунд для работы сервера

Вы можете указать число секунд, в течении которых будут выполняться задачи - например, для того, чтобы поставить queue:listen в cron на запуск раз в минуту.

php artisan queue:listen --timeout=60

Уменьшение частоты опроса очереди

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

php artisan queue:listen --sleep=5

Если очередь пуста, она будет опрашиваться раз в 5 секунд. Если в очереди есть задачи, они исполняются без задержек.

Обработка только первой задачи в очереди

Для обработки только одной (первой) задачи можно использовать команду queue:work:

php artisan queue:work

Обработчик очереди в режиме демона

Команда queue:listen вызывает команды queue:work, которая, как и при HTTP-запросе браузера, инициализирует фреймворк, вызывает метод класса или функцию-замыкание, десериализованную из очереди, и затем завершает работу.

Альтернатива - команда queue:work с ключом --daemon. В этом случае фреймворк будет загружен один раз и при переходе к следующей задаче не будет перезагружаться, а исполнит её после выполнения предыдущей. В этом случае мы сильно экономим CPU, но такой подход накладывает определенные ограничения - нужно следить за расходом памяти и рестартовать демона после деплоя (заливки на хостинг) нового кода.

Запустить обработчика очереди в демон-режиме:

php artisan queue:work connection --daemon

php artisan queue:work connection --daemon --sleep=3

php artisan queue:work connection --daemon --sleep=3 --tries=3

Как видно, команда queue:work поддерживет те же опции, что и queue:listen. Для подробного хелпа по опциям смотрите вывод команды php artisan help queue:work.

Деплой приложения с обработчиком-демоном

Самый простой способ корректно деплоить приложение, в которых используются обработчики-демоны - переводить его перед деплоем в режим обслужиания (maintenance mode) командой php artisan down. В этом состоянии Laravel не берет новые задачи из очереди, а только продолжает исполнять уже взятые задачи.

Чтобы рестартовать обработчиков очереди, воспользуйтесь командой queue:restart

php artisan queue:restart

Эта команда заставит всех обработчиков-демонов перезапуститься после завершения выполнения их текущей задачи.

Примечание: Команда перезапуска помещается в кэш и каждый обработчик-демон проверяет этот ключ в кэше перед началом выполнения задачи. Если вы используете в качестве кэша APC, то имейте в виду, что по умолчанию он отключен для скриптов, запускаемых из командной строки (в том числе и для обработчиков-демонов), поэтому не забудьте включить его в конфиге APC - apc.enable_cli=1

Программирование задач с учетом обработчиков-демонов

При разработке задач, которые будут исполняться обработчиками-демонами, вы должны вручную освобождать все задействованные ресурсы до завершения задачи. Например, если вы обрабатываете изображения при помощи библиотеки GD, освобождайте память при помощи imagedestroy в конце задачи.

Также соединение с БД через некоторое время рвется, поэтому не забывайте в начале задачи его возобновлять при помощи DB::reconnect.

Push-очереди

Push-очереди дают вам доступ ко всем мощным возможностям, предоставляемым подсистемой очередей Laravel без запуска серверов или фоновых программ. На текущий момент push-очереди поддерживает только драйвер Iron.io. Перед тем, как начать, создайте аккаунт и впишите его данные в app/config/queue.php.

Регистрация подписчика push-очереди

После этого вы можете использовать команду queue:subscribe Artisan для регистрации URL точки (end-point), которая будет получать добавляемые в очередь задачи.

php artisan queue:subscribe queue_name http://foo.com/queue/receive

Теперь, когда вы войдёте в ваш профиль Iron, то увидите новую push-очередь и её URL подписки. Вы можете подписать любое число URL на одну очередь. Дальше создайте маршрут для вашей точки queue/receive и пусть он возвращает результат вызова метода Queue::marshal:

Route::post('queue/receive', function()
{
    return Queue::marshal();
});

Этот метод позаботится о вызове нужного класса-обработчика задачи. Для помещения задач в push-очередь просто используйте всё тот же метод Queue::push, который работает и для обычных очередей.

Незавершённые задачи

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

Laravel имеет средства для контроля над некорректным завершением задач. Если прошло заданное максимальное количество попыток запуска и задача ни разу не исполнилась до конца, завершившись исключением, она пимещается в базу данных, в таблицу failed_jobs. Изменить название таблицы вы можете в конфиге config/queue.php.

Данная команда создает миграцию для создания таблицы в вашей базе данных:

php artisan queue:failed-table

Максимальное число попыток запуска задачи задается параметром --tries команды queue:listen:

php artisan queue:listen connection-name --tries=3

Вы можете зарегистрировать слушателя события Queue::failing, чтобы, например, получать уведомления по e-mail, что что-то в подсистеме очередей у вас идет не так:

Queue::failing(function($connection, $job, $data)
{
    //
});

Вы также можете создать в классе команды метод failed, позволяющий выполнить какие-либо действия при неудачной попытке выполнения задачи:

public function failed()
{
    // 
}

Получение незавершённых задач

Список всех незавершённых задач c их ID вам покажет команда queue:failed:

php artisan queue:failed

Вы можете вручную рестартовать задачу по её ID:

php artisan queue:retry 5

Если вы хотите удалить задачу из списка незавершённых, используйте queue:forget:

php artisan queue:forget 5

Чтобы очистить весь список незавершённых задач, используйте queue:flush:

php artisan queue:flush