Ошибки и повторное выполнение

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

Для того чтобы это сделать существует несколько способов.

Опции повторного выполнения

Первый способ реализован глобальными настройками компонента:

'components' => [
    'queue' => [
        'class' => \yii\queue\<driver>\Queue::class,
        'ttr' => 5 * 60, // Максимальное время выполнения задания 
        'attempts' => 3, // Максимальное кол-во попыток
    ],
],

Опция ttr устанавливает резервное время для выполнения заданий. Перед выполнением задание попадает в резерв и будет находиться там не дольше чем задано в ttr. Если задание не выполнилось успешно, и требуется повторная попытка, оно вернется назад в очередь. Если выполнилось - будет удалено из резерва. Опция attempts устанавливает максимальное количество попыток. Если попытки закончились, и задание не выполнилось удачно, оно так же будет удалено из резерва.

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

Описанные опции действуют глобально на все задания в очереди. Чтобы переопределить поведение для отдельных заданий, существуют дополнительные возможности.

RetryableJobInterface

Индивидуальный контроль повторного выполнения реализован интерфейсом RetryableJobInterface, код такого job-объекта может выглядеть следующим образом:

class SomeJob extends BaseObject implements RetryableJobInterface
{
    public function execute($queue)
    {
        //...
    }

    public function getTtr()
    {
        return 15 * 60;
    }

    public function canRetry($attempt, $error)
    {
        return ($attempt < 5) && ($error instanceof TemporaryException);
    }
}

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

Обработчики событий

Еще один способ задать резервное время и необходимость повторного запуска невыполненной задачи предполагает использование событий Queue::EVENT_BEFORE_PUSH и Queue::EVENT_AFTER_ERROR.

Событие Queue::EVENT_BEFORE_PUSH можно использовать, чтобы задать резервное время:

Yii::$app->queue->on(Queue::EVENT_BEFORE_PUSH, function (PushEvent $event) {
    if ($event->job instanceof SomeJob) {
        $event->ttr = 300;
    }
});

А событие Queue::EVENT_AFTER_ERROR — чтобы определить задание на повторную попытку:

Yii::$app->queue->on(Queue::EVENT_AFTER_ERROR, function (ExecEvent $event) {
    if ($event->job instanceof SomeJob) {
        $event->retry = ($event->attempt < 5) && ($event->error instanceof TemporaryException);
    }
});

Обработчики событий выполняются после методов RetryableJobInterface и, следовательно, имеют наивысший приоритет.

Ограничения

Не все драйверы одинаково хорошо поддерживают повторное выполнение заданий. Полноценную поддержку обеспечивают драйвера: Beanstalk, DB, File и Redis. Синхронный драйвер, как отладочный, не будет повторять невыполненные задания. Gearman не поддерживает повторное выполнение вообще. А RabbitMQ имеет только свою базовую поддержку повторов, при которой номер попытки узнать не получится.

В AWS SQS для обработки сообщений, которые не могут быть обработаны, используется Dead Letter очередь. В эту очередь попадают сообщения, которые не обработались после максимального количества попыток. Указать Dead Letter очередь и максимальное количество попыток можно при создании очереди через консоль AWS.