Системи обміну повідомленнями дають одну з гарантій доставки:
- щонайбільше один раз (at-most-once) - повідомлення може загубитися, але дубліката не буде;
- щонайменше один раз (at-least-once) - повідомлення не загубиться, але може прийти кілька разів;
- рівно один раз (exactly-once) - ідеал, який у розподілених системах на практиці досягається лише в обмежених умовах.
Більшість черг і брокерів (Laravel Queue з Redis/SQS/database, RabbitMQ, Kafka в типовій конфігурації) працюють за моделлю at-least-once.
Звідки беруться дублікати:
- воркер отримав джобу й успішно виконав її (надіслав лист);
- перед підтвердженням обробки (видаленням із черги) воркер упав, з'єднання обірвалося чи перевищено тайм-аут;
- черга вважає джобу необробленою й видає її знову - лист надіслано двічі.
Також дублікати виникають, коли відправник повторює відправку після тайм-ауту, не знаючи, що перша спроба дійшла.
Наслідок для коду: обробник має бути ідемпотентним - повторна обробка того самого повідомлення не повинна мати додаткового ефекту.
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: обмін повідомленнями