АрхитектураРаспределённые системыИнженер распределённых систем

В очереди сообщений обработчик завершил бизнес операцию, но процесс упал до подтверждения сообщения. Какую ...

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

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

Следует ожидать семантику at-least-once: сообщение может быть доставлено повторно. Брокер не видит подтверждения и обычно считает обработку незавершённой, поэтому повторяет доставку после тайм-аута, переподключения или перезапуска потребителя.

Обработчик должен быть идемпотентным: повторная обработка одного логического сообщения не должна повторно создавать бизнес-эффект. Подтверждать сообщение следует только после надёжной фиксации результата обработки.

Исторический контекст

Очереди сообщений применяются для развязки производителей и потребителей, сглаживания нагрузки и переживания временной недоступности сервисов. Для этого брокеру требуется механизм подтверждения: потребитель сообщает, что сообщение принято и обработано.

Если подтверждение не получено, безопаснее повторить доставку, чем окончательно потерять сообщение. Так сформировалась практическая модель at-least-once, которая предпочитает возможные дубликаты риску потери данных.

Постановка проблемы

Между выполнением бизнес-операции и отправкой подтверждения существует окно отказа. Например, обработчик записал заказ в базу, но завершился до подтверждения сообщения.

После восстановления брокер повторно передаст сообщение. Без защиты это может привести к двойному списанию, повторному созданию заказа, дублирующему уведомлению или повторной публикации события.

Раннее подтверждение устраняет дубликаты, но создаёт обратную опасность: если процесс подтвердил сообщение до выполнения операции и затем упал, сообщение может быть потеряно. Поэтому выбор момента подтверждения — это компромисс между потерей и повторной обработкой.

Подробное решение

При модели at-least-once последовательность обычно такова: потребитель получает сообщение, выполняет операцию, надёжно фиксирует её результат и только после этого отправляет подтверждение брокеру. Сбой до подтверждения вызывает повторную доставку, поэтому повторная обработка должна распознаваться по стабильному идентификатору сообщения или операции.

Для защиты используют идемпотентность и дедупликацию. Потребитель хранит идентификатор уже применённой операции или применяет изменение с уникальным ограничением в той же транзакции, что и бизнес-данные. Повторная доставка тогда приводит к проверяемому отсутствию нового эффекта, после чего сообщение можно подтвердить.

Это не означает, что брокер гарантирует глобальную семантику exactly-once. Если обработчик изменяет внешнюю систему, например платёжный шлюз, локальная транзакция базы данных не делает внешний вызов атомарным с записью результата. В таком случае нужны поддержка идемпотентных ключей внешней операции, устойчивый журнал намерений или согласованный процесс с возможностью повторной проверки результата.

У дедупликации есть цена: требуется долговечное хранилище идентификаторов, политика его очистки и корректный выбор области уникальности. Слишком раннее удаление записи о сообщении позволит повторить эффект, а бесконечное хранение увеличит объём данных. Для операций, которые нельзя безопасно повторять, полезно моделировать переходы состояния так, чтобы повторный запрос был допустим только из ожидаемого состояния.

Ситуация из практики

Сервис обработки заказов получает сообщение о необходимости создать отгрузку. Он записывает отгрузку в базу, но падает перед подтверждением сообщения. При повторной доставке наивный обработчик создаёт вторую отгрузку для того же заказа.

Рассматривались три варианта. Подтверждать сообщение до записи в базу быстро и исключает дубликаты, но допускает потерю отгрузки. Пытаться добиться exactly-once только настройками брокера недостаточно: брокер не контролирует транзакцию базы данных. Оставить подтверждение после записи и не менять обработчик проще всего, но дубликаты останутся.

Выбран вариант at-least-once с идемпотентным обработчиком: идентификатор заказа используется как ключ уникальности, а запись отгрузки и фиксация обработанного идентификатора выполняются атомарно в одной транзакции базы данных. После успешной транзакции отправляется подтверждение. При повторной доставке уникальность предотвращает вторую отгрузку, а сбои приводят лишь к повторной проверке состояния.

Что кандидаты часто упускают

  1. Можно ли устранить дубликаты, отправляя подтверждение сразу после получения сообщения?

Нет, это меняет риск, но не решает задачу. При падении после подтверждения и до бизнес-операции брокер уже не обязан доставлять сообщение повторно, поэтому возникает потеря обработки. Такой режим соответствует приближённой семантике at-most-once и подходит только тогда, когда потеря допустима или операция будет восстановлена другим механизмом.

  1. Достаточно ли уникального идентификатора сообщения для идемпотентности?

Только если все повторные доставки одной логической операции используют один и тот же стабильный идентификатор. Если производитель создаёт новый идентификатор при каждом повторе, потребитель не сможет распознать дубликат. Кроме того, ключ должен соответствовать бизнес-операции: разные сообщения могут требовать разных эффектов, а одинаковый заказ не всегда означает одинаковую допустимую операцию.

  1. Что произойдёт, если два экземпляра потребителя одновременно обработают одну повторно доставленную операцию?

Проверка «идентификатор ещё не обработан» без атомарной фиксации создаёт гонку: оба экземпляра могут пройти проверку и выполнить эффект. Запись результата и регистрация идентификатора должны защищаться уникальным ограничением, транзакцией или другой атомарной операцией. При конфликте один экземпляр фиксирует результат, а второй распознаёт повтор и безопасно подтверждает сообщение.