Москва: проверенные б/у ноутбуки с гарантией и проверкой перед оплатой
+7 906 722-94-45

Главная → Статьи → Порядок обработки сообщений и его гарантии

Порядок обработки сообщений: какие гарантии дают брокеры и как их не потерять

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

Прямой ответ

Гарантия порядка сообщений почти всегда частичная: порядок сохраняется в пределах одного раздела или одной очереди, но не глобально. Понимание границы гарантии определяет проектирование обработчика.

Гарантии разных брокеров:

Брокер Где сохраняется порядок Что его нарушает
Kafka В пределах раздела Разные разделы для связанных сообщений
RabbitMQ В пределах очереди при одном потребителе Несколько параллельных потребителей
NATS JetStream В пределах потока при одном потребителе Параллельные потребители

Kafka записывает сообщения в разделы, и внутри раздела порядок сохраняется. Ключ партиционирования определяет, в какой раздел попадёт сообщение:

producer.send("orders", key=b"order-1001", value=payload)

Все сообщения заказа 1001 попадают в один раздел и читаются в порядке записи. Сообщения разных заказов могут идти в разные разделы и обрабатываться в произвольном порядке — это нормально, если заказы независимы; о механизме — в статье про основы Kafka.

Основной источник потери порядка — параллельная обработка. Даже при упорядоченной доставке обработчик может нарушить порядок:

доставка:      сообщение 1 -> сообщение 2 -> сообщение 3
обработчики:   поток A (сообщение 1), поток B (сообщение 2)
сообщение 2 обработано быстрее, результат записан раньше

Гарантия брокера распространяется на доставку, а не на завершение обработки. Параллельные обработчики дают пропускную способность ценой порядка.

Три подхода к работе с порядком.

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

2. Номер версии в сообщении. Обработчик отклоняет сообщение с версией ниже текущей:

if message.version <= entity.version:
    return  # устаревшее событие
entity.version = message.version

Подход делает обработчик устойчивым к нарушению порядка, а не требует его соблюдения.

3. Последовательная обработка. Один обработчик на раздел сохраняет порядок, но ограничивает пропускную способность.

На практике сочетают первый и второй подходы: ключ партиционирования снижает частоту нарушения порядка, а проверка версии делает нарушение безопасным; о согласованности — в статье про eventual consistency.

Практика: проектирование с учётом порядка

1. Определите, в каких пределах нужен порядок. Глобальный порядок требуется редко; обычно достаточно порядка в пределах сущности.

2. Задавайте ключ партиционирования по сущности. Сообщения одного заказа, одного пользователя либо одного документа идут в один раздел.

3. Добавляйте номер версии либо метку времени. Обработчик отклоняет устаревшие события вместо применения их поверх новых.

4. Не увеличивайте число обработчиков без проверки. Рост параллелизма нарушает порядок, который обеспечивался единственным потребителем.

5. Делайте обработку идемпотентной. Повторная доставка возможна при гарантии «не менее одного раза»; о механизме — в статье про идемпотентность.

6. Проверяйте поведение при повторе раздела. После перебалансировки часть сообщений может быть обработана повторно и в ином порядке.

7. Фиксируйте последнюю обработанную позицию. Смещение потребителя позволяет определить, какие сообщения обработаны.

8. Тестируйте нарушение порядка. Тест с доставкой событий в обратном порядке проверяет, что обработчик отклоняет устаревшие.

Рекомендации:

  1. Не полагайтесь на порядок между разделами. Гарантии нет, и проектирование в расчёте на неё приводит к трудноуловимым ошибкам.
  2. Различайте порядок доставки и порядок применения. Обработчик может применить сообщения в ином порядке, чем получил.
  3. Учитывайте задержку между событием и обработкой. Событие, созданное раньше, может прийти позже из-за задержки в промежуточных системах.
  4. Проверяйте монотонность версии. Версия должна расти, иначе проверка устаревания не работает.
  5. Документируйте гарантию для каждого потока. Потребитель должен знать, какой порядок ожидать.

Практический сценарий: откат состояния заказа из-за нарушения порядка событий

Контекст: статус заказа периодически возвращался к более раннему значению. События публиковались в один поток, и доставка была упорядоченной.

  1. Диагностика. Обработчик выполнялся в нескольких потоках для пропускной способности. События «оплачен» и «отгружен» доставлялись по порядку, но обрабатывались параллельно, и обновление для «оплачен» записывалось позже.
  2. Причина. Гарантия брокера распространялась на доставку, а не на завершение обработки. Параллелизм в обработчике нарушил порядок применения, а проверки версии в сообщении не было.
  3. Действия. (а) Добавили номер версии в каждое событие и проверку перед применением: событие с версией не выше текущей отклоняется; (б) ключ партиционирования установили по идентификатору заказа, чтобы события заказа шли в один раздел; (в) параллельную обработку сохранили, поскольку проверка версии сделала нарушение порядка безопасным; (г) добавили тест с доставкой событий в обратном порядке; (д) добавили метрику числа отклонённых устаревших событий.
  4. Результат. Состояние заказа больше не откатывается, а пропускная способность обработки сохранилась благодаря параллелизму.

Вывод: гарантия порядка брокера заканчивается на доставке. Параллельная обработка нарушает порядок применения, поэтому защита строится на проверке версии события, а не на предположении об упорядоченности.

FAQ

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

Как получить порядок для конкретной сущности? Ключом партиционирования по идентификатору сущности: все её сообщения попадают в один раздел.

Что делать, если параллелизм необходим? Добавить номер версии в событие и отклонять устаревшие. Это сохраняет пропускную способность без требования к порядку.

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

Источники: Kafka docs (концепции и термины), RabbitMQ docs (Queues), NATS docs (JetStream) — ссылки в шапке.