Выполнение одного и того же задания в очереди несколько раз

laravel

Когда пользователи добавляют товары в свою корзину покупок и начинают процесс оформления заказа, нам необходимо зарезервировать эти товары для них. Однако если пользователь отказался от заказа — он не отменял и не оплачивал его, нам потребуется вернуть зарезервированные товары обратно на склад, чтобы другие люди могли их заказать.

Для решения этой задачи мы собираемся использовать очереди laravel. Мы сделаем job, который через час после добавления в корзину покупателем будет проверять, была ли оплачена корзина, или она брошена. Если брошена, то job автоматически вернет товар на склад.

Задержка обработки задания

Ниже пример того, как оформляется корзина и создается job с задержкой в 1 час:

class CheckoutController
{
    public function store()
    {
        $order = Order::create([
            'status' => Order::PENDING,
            // ...
        ]);

        dispatch(new MonitorPendingOrder($order))
        ->delay(3600);
    }
}

Можно также указывать задержку с использованием  DateTimeInterface объекта:

dispatch(new MonitorPendingOrder($order))
->delay(
    now()->addHour()
);

Используя драйвер SQS, вы можете задержать выполнение задания только на 15 минут. Если вы хотите задержать задания еще на несколько минут, вам нужно будет сначала задержать их на 15 минут, а затем продолжать выпускать задание обратно в очередь с помощью release(). Вы также должны знать, что SQS хранит задание только в течение 12 часов после того, как оно было поставлено в очередь.

Ниже пример хендлера нашего обработчика задания (job):

public function handle()
{
    if ($this->order->status == Order::CONFIRMED ||
        $this->order->status == Order::CANCELED){
        return ;
    }

    $this->order->markAsCanceled();
}

Когда задание будет выполнено через час— мы проверим, был ли заказ отменен или подтвержден, и просто вернемся из handle()метода. Использование return заставит воркера считать задание успешным и удалить его из очереди.

Но что делать с брошенными корзинами? Все просто. Если в течении часа не было никаких движений с корзиной, значит она удаляется, а товары возвращаются на склад.

Отправка пользователям напоминания перед отменой

Возможно, было бы неплохо отправить пользователю SMS-уведомление, чтобы напомнить ему о своем заказе, прежде чем полностью отменить его. Поэтому давайте отправлять SMS каждые 15 минут, пока пользователь не завершит оформление заказа или мы отменим заказ через 1 час.

Для этого мы собираемся отложить отправку задания на 15 минут вместо часа:

dispatch(new MonitorPendingOrder($order))
->delay(
    now()->addMinutes(15)
);

Когда задание выполняется, мы хотим проверить прошел ли час и отменить заказ.

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

public function handle()
{
    if ($this->order->status == Order::CONFIRMED ||
        $this->order->status == Order::CANCELED){
        return;
    }

    if ($this->order->olderThan(59, 'minutes')){
        $this->order->markAsCanceled();

        return;
    }

    SMS::send(...);

    $this->release(
        now()->addMinutes(15)
    );
}

Использование release()внутри задания имеет тот же эффект, что и использование delay()во время диспетчеризации. Задание будет выпущено обратно в очередь, и воркеры снова запустят его через 15 минут.

Защита от бесконечных попыток

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

class MonitorPendingOrder implements ShouldQueue
{
    public $tries = 4;
}

Теперь это задние будет выполняться:

15 minutes after checkout
30 minutes after checkout
45 minutes after checkout
60 minutes after checkout

Если пользователь подтвердил или отменил заказ скажем через 20 минут, то задание будет удалено из очереди при его запуске на попытку через 30 минут и SMS не будет отправлено.

Это происходит потому, что у нас есть эта проверка в начале handle()метода:

if ($this->order->status == Order::CONFIRMED ||
    $this->order->status == Order::CANCELED){
    return;
}

Записка о задержках в работе

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

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

Рейтинг
( 1 оценка, среднее 5 из 5 )
Maxyc Webber/ автор статьи
Мне 35 лет. Опыт профессиональной разработки 15 лет. Занимаюсь разработкой и поддержкой корпоративных систем автоматизации бизнеса, а также высоконагруженными проектами. Мне нравится решать нестандартные проблемы бизнеса. Имею опыт формирования команд под проект, налаживания процесса разработки, коммуникации программистов и заказчиков. Есть опыт работы с зарубежными заказчиками (ОАЭ, Польша, Германия, Швейцария).
Понравилась статья? Поделиться с друзьями:
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!:

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.