Основы дизайна систем: обмен сообщениями и Pub-Sub

Когда вы проектируете и строите крупномасштабные и распределенные системы, чтобы эта система работала согласованно и бесперебойно, важно обмениваться информацией между компонентами и службами, составляющими систему. Но системы, которые полагаются на сети, страдают той же слабостью, что и сами сети - они хрупки. Сети выходят из строя, и это не редкость. Когда сети выходят из строя, компоненты в системе не могут обмениваться данными и могут вывести систему из строя (в лучшем случае) или вызвать полный отказ системы (в худшем случае). Таким образом, распределенные системы нуждаются в надежных механизмах для обеспечения продолжения или восстановления связи с того места, где оно было прервано, даже если существует «произвольный раздел» (то есть сбой) между компонентами в системе.

Представьте, например, что вы бронируете авиабилеты. Вы получаете хорошую цену, выбираете места, подтверждаете бронирование и даже платите кредитной картой. Теперь вы ждете, пока ваш PDF-файл с билетами не поступит в ваш почтовый ящик. Вы ждете и ждете, и этого никогда не произойдет. Где-то произошел системный сбой, который не удалось обработать или восстановить должным образом. Система бронирования часто соединяется с API авиакомпаний и ценообразования для обработки фактического выбора рейса, сводки тарифов, даты и времени полета и т. д. Все это делается, когда вы нажимаете на пользовательский интерфейс бронирования сайта. Но он не обязан отправлять вам билеты в формате PDF только через несколько минут. Вместо этого пользовательский интерфейс может просто подтвердить, что ваше бронирование выполнено, и вы можете ожидать билеты в своем почтовом ящике в ближайшее время. Это разумный и распространенный пользовательский интерфейс для бронирований, поскольку момент оплаты и получения билетов не обязательно должны быть одновременными - два события могут быть асинхронными. Такой системе потребуется обмен сообщениями, чтобы гарантировать, что служба (конечная точка сервера), которая асинхронно генерирует PDF-файл, получит уведомление о подтвержденном оплаченном бронировании и всех деталях, а затем PDF-файл может быть автоматически сгенерирован и отправлен вам по электронной почте. Но если эта система обмена сообщениями выйдет из строя, служба электронной почты никогда не узнает о вашем бронировании, и билет не будет создан.

Обмен сообщениями издателя / подписчика (Publisher / Subscriber)

Это очень популярная парадигма (модель) обмена сообщениями. Ключевой концепцией является то, что издатели «публикуют» сообщение, а подписчик подписывается на сообщения. Для большей детализации сообщения могут принадлежать определенной «теме», которая подобна категории. Эти темы похожи на выделенные «каналы» или каналы, где каждый эксклюзивный канал обрабатывает сообщения, принадлежащие определенной теме. Подписчики выбирают, на какую тему они хотят подписаться, и получают уведомления о сообщениях в этой теме. Преимущество этой системы в том, что издатель и подписчик могут быть полностью разделены, т.е. им не нужно знать друг о друге. Издатель объявляет, и подписчик слушает объявления по темам, которые он ищет.

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

Сообщения в теме - это просто данные, которые необходимо передать, и они могут принимать любые нужные вам формы. Таким образом, вы получаете четырех игроков в Pub/Sub: Издатель, Подписчик, Темы и Сообщения.

Лучше, чем база данных

Так зачем с этим возиться? Почему бы просто не сохранить все данные в базе данных и не использовать их прямо оттуда? Что ж, вам нужна система для постановки сообщений в очередь, потому что каждое сообщение соответствует задаче, которую необходимо выполнить на основе данных этого сообщения. Итак, в нашем примере с билетами, если 100 человек бронируют билеты за 35 минут, занесение всего этого в базу данных не решает проблемы отправки электронной почты этим 100 людям. Она просто хранит 100 транзакций. Системы Pub/Sub обрабатывают обмен данными, последовательность задач, а сообщения сохраняются в базе данных. Таким образом, система может предлагать полезные функции, такие как доставка "хотя бы один раз" (сообщения не будут потеряны), постоянное хранение, упорядочение сообщений, "повторная попытка", "возможность повторного воспроизведения" сообщений и т. д. Без этой системы просто Сохранение сообщений в базе данных не поможет вам гарантировать, что сообщение будет доставлено (использовано) и будут приняты меры для успешного выполнения задачи.

Иногда одно и то же сообщение может быть использовано подписчиком более одного раза - обычно из-за того, что сеть на мгновение отключилась, и, хотя подписчик использовал сообщение, он не сообщил об этом издателю. Так что издатель просто повторно отправит его подписчику. Поэтому гарантия - «хотя бы раз», а не «раз и только раз». Это неизбежно в распределенных системах, потому что сети по своей природе ненадежны. Это может вызвать сложности, когда сообщение запускает операцию на стороне подписчика, и эта операция может изменить что-то в базе данных (изменить состояние во всем приложении). Что делать, если одна операция повторяется несколько раз и каждый раз при изменении состояния приложения?

Контроль результатов - один или несколько результатов?

Решение этой новой проблемы называется идемпотентностью - это концепция, которая важна, но не интуитивно понятна для понимания в первые несколько раз, когда вы ее исследуете. Эта концепция может показаться сложной (особенно если вы читаете статью в Википедии), поэтому для текущей цели вот удобное для пользователя упрощение от StackOverflow:

В вычислениях идемпотентная операция - это операция, которая не имеет дополнительного эффекта, если она вызывается более одного раза с одними и теми же входными параметрами.

Поэтому, когда подписчик обрабатывает сообщение два или три раза, общее состояние приложения точно такое, каким оно было после того, как сообщение было обработано в первый раз. Если, например, в конце бронирования авиабилетов и после того, как вы ввели данные своей кредитной карты, вы трижды нажали «Оплатить сейчас», потому что система работала медленно ... вы не захотите платить в 3 раза больше цены билета, правильно? Идемпотентность необходима, чтобы гарантировать, что каждый щелчок после первого не приведет к повторной покупке и списанию средств с кредитной карты более одного раза. Напротив, вы можете разместить идентичный комментарий в ленте новостей вашего лучшего друга N раз. Все они будут отображаться в виде отдельных комментариев. Другой пример - предложение «аплодисментов» на постах Medium - каждый хлопок предназначен для увеличения количества хлопков, а не для одного и только одного хлопка. Эти последние два примера не требуют идемпотентности, но пример оплаты требует.

Существует множество разновидностей систем обмена сообщениями, и выбор системы определяется сценарием использования, который необходимо решить. Часто люди будут ссылаться на архитектуру «на основе событий», что означает, что система полагается на сообщения о «событиях» (например, оплата билетов) для обработки операций (например, отправка билета по электронной почте). Наиболее часто упоминаемые сервисы - это Apache Kafka, RabbitMQ, Google Cloud Pub/Sub, AWS SNS/SQS.


Читайте также:

Комментарии

Популярные сообщения из этого блога

Язык поисковых запросов в Graylog

Нормальные формы, пример нормализации в базе данных

Хэш-таблица: разрешение коллизий