Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама

Що означає доставка «щонайменше один раз» у чергах і брокерах повідомлень?

Системи обміну повідомленнями дають одну з гарантій доставки:

  • щонайбільше один раз (at-most-once) - повідомлення може загубитися, але дубліката не буде;
  • щонайменше один раз (at-least-once) - повідомлення не загубиться, але може прийти кілька разів;
  • рівно один раз (exactly-once) - ідеал, який у розподілених системах на практиці досягається лише в обмежених умовах.

Більшість черг і брокерів (Laravel Queue з Redis/SQS/database, RabbitMQ, Kafka в типовій конфігурації) працюють за моделлю at-least-once.

Звідки беруться дублікати:

  1. воркер отримав джобу й успішно виконав її (надіслав лист);
  2. перед підтвердженням обробки (видаленням із черги) воркер упав, з'єднання обірвалося чи перевищено тайм-аут;
  3. черга вважає джобу необробленою й видає її знову - лист надіслано двічі.

Також дублікати виникають, коли відправник повторює відправку після тайм-ауту, не знаючи, що перша спроба дійшла.

Наслідок для коду: обробник має бути ідемпотентним - повторна обробка того самого повідомлення не повинна мати додаткового ефекту.

public function handle(): void
{
    $payment = Payment::find($this->paymentId);

    if ($payment->isCaptured()) {
        return;   // уже оброблено - нічого не робимо
    }

    $this->gateway->capture($payment);
    $payment->markCaptured();
}

Способи досягти ідемпотентності:

  • перевірка стану перед дією (як вище);
  • унікальний ключ обробленого повідомлення в базі (processed_messages з унікальним індексом);
  • ідемпотентні операції за природою: «встановити статус paid» замість «додати 100 до балансу»;
  • ключ ідемпотентності у викликах зовнішніх API (Stripe приймає Idempotency-Key).

Налаштування Laravel, що впливають на дублікати:

  • retry_after у з'єднанні черги має бути більшим за timeout джоби, інакше черга видасть джобу іншому воркеру, поки перший ще працює;
  • tries, backoff - скільки разів і з якою паузою повторювати;
  • ShouldBeUnique - не ставити в чергу джобу, якщо така сама вже чекає.

Головна думка: «рівно один раз» - не властивість черги, а результат at-least-once доставки + ідемпотентної обробки.

Докладніше в документації: Microservices.io: обмін повідомленнями

Схожі питання