Как защититься от потери сообщений при даунтайме месседж брокера?
Что проверяют
Понимаете ли вы, что сообщения теряются в трёх местах: у продюсера, в брокере и у консьюмера, и что даунтайм брокера бьёт прежде всего по продюсеру. Следом спрашивают, как не потерять событие, если приложение упало сразу после записи в базу.
Как отвечать
Разложите ответ по звеньям:
- продюсер: главный паттерн это transactional outbox. Событие пишется в таблицу
outboxв той же транзакции, что и бизнес-данные, а отдельный процесс или Debezium отправляет его в брокер, когда тот доступен. Даунтайм превращается в задержку, а не в потерю; - буфер клиента:
confluent-kafkaдержит неотправленное в памяти и повторяет попытки доmessage.timeout.ms, по умолчанию 5 минут. Итог приходит в колбэк доставки, а перед остановкой нуженflush(). Буфер в памяти умирает вместе с процессом, так что outbox он не заменяет; - брокер: в Kafka
acks=all, фактор репликации 3 иmin.insync.replicas=2. В RabbitMQ durable-очереди, persistent-сообщения (delivery_mode=2), quorum-очереди на нескольких узлах и publisher confirms, чтобы знать, что брокер принял сообщение; - консьюмер: подтверждение только после обработки. В Celery это
task_acks_late=Trueиtask_reject_on_worker_lost=True, в Kafka коммит оффсета после обработки. Получается доставка «хотя бы один раз», поэтому обработчик делают идемпотентным, например по уникальному id сообщения в базе; - ядовитые сообщения: ограниченное число ретраев с backoff и dead letter очередь.
Где спотыкаются
Надеются на pub/sub. Сообщение в fanout-обменник RabbitMQ без привязанных очередей просто выбрасывается, а Redis Pub/Sub вообще ничего не хранит.
Отправляют событие сразу после коммита без outbox. Процесс упал между двумя операциями, и база с брокером разошлись.
Обещают «ровно один раз» из коробки. Идемпотентный продюсер и транзакции Kafka дают это только внутри Kafka, а побочные эффекты в базе и внешних API всё равно требуют дедупликации.