Гарантии доставки: at-most-once, at-least-once, exactly-once

↑ 5.4 Очереди и брокеры: Kafka, RabbitMQ · ← Предыдущая · → Следующая

Обязательно~3 мин чтенияУровень: middle

Зачем это на собесе

«Что значит exactly-once?» — ловушка: на практике это at-least-once + идемпотентность.

Объяснение

ГарантияПоведениеКак достигаетсяРиск
At-most-onceдоставлено 0 или 1 разне повторять; коммит до обработкипотеря сообщений
At-least-onceдоставлено ≥1 разповторять до подтверждения; коммит после обработкидубли
Exactly-onceровно 1 эффектat-least-once + идемпотентная обработка/транзакциисложность

Распределённая система не может отличить «сообщение потеряно» от «ответ потерян», поэтому настоящая доставка «ровно один раз» на уровне сети невозможна. Достижим эффект «ровно один раз»: дубли приходят, но повторная обработка не меняет результат.

Способы получить exactly-once эффект:

  • Идемпотентный потребитель: дедупликация по id сообщения (Inbox) или естественно идемпотентные операции (upsert, set status = paid).
  • Транзакции Kafka (consume-transform-produce внутри Kafka).
  • Transactional outbox для надёжной публикации (см. Transactional Outbox и Inbox, идемпотентные консьюмеры).
  • Оптимистическая проверка версии состояния.
// Идемпотентная обработка: таблица обработанных сообщений в той же транзакции, что и эффект
await using var tx = await db.Database.BeginTransactionAsync(ct);
if (await db.Processed.AnyAsync(p => p.MessageId == msg.Id, ct)) return;   // дубль
await ApplyAsync(msg, ct);
db.Processed.Add(new Processed(msg.Id));
await db.SaveChangesAsync(ct);
await tx.CommitAsync(ct);

Нюансы и подводные камни

  • «Exactly-once» в Kafka работает в границах Kafka; внешние эффекты (email, платёж) требуют идемпотентности.
  • Дедупликация по id нуждается в TTL и хранилище.
  • Порядок и повторы: старое сообщение может прийти после нового — проверяйте версию.
  • Побочные эффекты (письма) отправляйте через outbox или идемпотентный ключ провайдера.

Практика

  1. Реализуйте Inbox-таблицу и проверьте дубль.
  2. Сымитируйте падение после обработки, но до коммита offset.
  3. Сделайте обработчик естественно идемпотентным через upsert.

Вопросы с ответами

Связанные темы