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

Начнем с определений. Система обмена сообщениями - это система, принимающая сообщение у отправителя и доставляющая его получателю. Мы можем написать систему обмена сообщениями сами, можем использовать ØMQ, а можем взять какую-нибудь монструозную MoM. Но любая из этих систем должна давать гарантии того, сколько раз отправленное сообщение может быть доставлено получателю при возникновении проблем. Гарантии определяют надежность доставки сообщения. Технически оправдано гарантировать один из следующих вариантов:

  • at-most-once - сообщение может быть доставлено 0 или 1 раз. Проще говоря сообщение может быть потеряно. Самый простой и эффективный способ доставки сообщений. Гарантий никаких.
  • at-least-once - сообщение может быть доставлено 1 и более раз. При ошибке доставки может быть предпринято несколько попыток отправить одно и то же сообщение. Поэтому могут возникнуть дубликаты, но потерянных сообщения быть не может. Этот способ сложнее чем at-most-once.
  • exactly-once - гарантированна доставка сообщения строго один раз. Именно этот вариант возникает по умолчанию в голове некоторых горе-программистов при упоминании любой системы обмена сообщениями. На практике же встречается достаточно редко.

Давайте разберемся на примере, что значат эти гарантии. Рассмотрим общий случай. Нам хочется построить отказоустойчивую систему. У нас имеется отправитель, система доставки сообщений (для определенности пусть это будет очередь) и получатели. Отправитель отправляет через очередь сообщение получателю. Получатель получив его падает. Это at-most-once. В большинстве случаев нас это не устраивает, поэтому получатель после получения посылает отправителю подтверждение. Если его нет, отправитель повторяет отправку сообщения. В случае, если подтверждение теряется, отправитель может отправить сообщение несколько раз и несколько раз оно будет обработано. Это at-least-once. Заметим, что в случае падения одного получателя мы можем перенаправить сообщение на другой получатель и, если первый получатель все же не умер, мы обработаем сообщение два раза.

Теперь подумаем, что будет, если мы захотим exactly-once. Просто перенаправить сообщение на другой обработчик мы уже не сможем. Нам нужно будет как-то маркировать сообщения при отправке и проверять их при получении, чтобы избежать дубликатов. Для того, чтобы правильно маркировать сообщения нам придется синхронизировать отправитель и получатель, что сведет часть преимуществ обмена сообщениями к нулю. В качестве альтернативного варианта мы можем придумать костыли на уровне бизнес-логики, например запретить получателям сохранять результат своей работы, если он уже присутствует в БД.

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

На этом все, надеюсь кому-нибудь заметка будет полезна.