В прошлых постах я несколько раз использовал фразу «взаимодействие контейнеров», но особо не вдаваясь в то, как на самом деле оно происходит, и, возможно, подавая ложную надежду, что речь идёт о телепатии.
Так как контейнеры изолированы друг от друга, то выбор транспорта для сообщений сильно ограничен, и, скорее всего, это будет сеть и TCP/UDP протоколы. Но при этом есть уйма вариантов, как этой сетью пользоваться, и об этом мы сейчас и поговорим.
Паттерны межсервисного общения
Всё просто. Их два — это синхронные и асинхронные сообщения.
Синхронные
Отправляя синхронное сообщение мы ожидаем, что тут же что-то придёт в ответ. Это может быть удалённый вызов процедуры (Remote Procedure Call, далее — RPC), или HTTP запрос к RESTful сервису, либо что-нибудь другое. Но в любом случае за запросом должен последовать ответ.
Синхронное общение заходит лучше всего. С удачным API создаётся впечатление, что на самым деле мы вызываем методы локального объекта, или отправляем запросы к локальной же базе данных. К несчастью, это всё ложь: на самом деле вызываемый объект лежит где-то далеко в сети, а сеть, господа, имеет тенденцию падать. Так уж она сделана. Но на дизайне приложения эта реальность никак не сказывается. Может, добавим дополнительный try-catch, и всё.
Еще одна проблема при использовании синхронного подхода кроется в том, что RPC и REST предполагают какое-то знание о вызываемой стороне. Но сервисам полагается быть независимыми — так в умных книгах пишется. В общем, проблема.
Асинхронное
Асинхронные сообщения подходят с другого боку. Вместо того, чтобы говорить удалённому сервису «а сделай-ка вот это», сервис-инициатор отправляет сообщение в космос «а вот было бы здорово, если бы…» не особо, впрочем, рассчитывая, что его молитвы будут услышаны. Но обычно боги микросервисов не дремлют, и кто-то слышит и реагирует.
Вот более атеистический пример этой же идеи. Например, пусть в нашем приложении будут два сервиса: UI и сервис обработки заказов. При синхронном подходе, как только UI-сервис заметит, что пользователь нажал кнопку «Заказать», он вызовет PlaceOrder метод в удалённом обработчике заказов через какой-нибудь RPC или REST.
При асинхронном подходе UI просто отправит сообщение «Тут приходили… Есть заказ..», но при этом ему не особо интересно, кто получит это сообщение и что он будет с этим делать.
Очередь сообщений
Но просто так отправить сообщение в космос не получится. У Маска не всегда получается, куда уж простому сельскому программисту. Поэтому выбирают цель поближе — очередь сообщений (Message Queue, MQ). Задача очереди — получить сообщение и держать его у себя, пока кто-нибудь не заберет. Как почтовый ящик. В ней могу быть дополнительные свистелки и ароматизаторы, но, в целом, основная идея именно такая.
И очередей на выбор — как медалей у позднего Брежнева:
- Простые, такие как ZeroMQ. Они даже не являются брокерами (посредниками), и работают прямо в сервисе.
- Полномасштабные, такие как RabbitMQ, MSMQ, IBM MQ или Kafka, которые масштабируются, поддерживают транзакции, подтверждения о доставке и другие полезности.
- Облачные решения, например Amazon SQS, Azure Queue и Service Bus Queue или Google Cloud Pub/Sub.
При таком изобилии выбор MQ для конкретного проекта может затянуться, поэтому стоит начать с простых наводящих вопросов, для чего же она нужна, и уже потом смотреть, что подходит. Например, если процесс с MQ скоропостижно скончался, а в нём оставались необработанные сообщения, важно ли, чтобы они сохранились? Если да, то ищем слово durable в характеристиках MQ. ZeroMQ, например, уходя в страну вечной охоты, уходит туда вместе со всеми сообщениями. MSMQ разрешает выбирать, что делать с сообщениями в проблемных случаях, а облачные MQ не умирают в принципе.
Еще один правдоподобный сценарий — сообщение всё-таки доставилось получателю, но он не успел его обработать и отключился. Стоит ли отправлять такое сообщение назад в очередь? Если да, то MSMQ, Kafka и другие поддерживают либо транзакции, либо подтверждения о доставке, с которыми сообщение можно спасти.
И, наконец, иногда сообщения отправляются… не туда. Например, очередь сообщений, куда целился отправитель, не существует, либо она была переполнена. Некоторые MQ поддерживают так называемую dead-letter queue (DLQ), которая создавалась именно под такие случаи.
Паттерны асинхронных сообщений
Кроме того, что сообщения можно отправлять, их можно отправлять по-разному. Например:
- Отправить-и-заб
иыть (он же fire-and-forget). Работает согласно названию. После отправки сообщения сервис не ожидает ничего в ответ. - Запрос-ответ (Request-response). Это паттерн уже немного напоминает RPC. После отправки сообщения сервис ожидает, что будет какая-нибудь ответная реакция. Для этого нужно уже две MQ — одна для исходящих и одна для входящих сообщений.
- Publish-subscribe (я сломался, переводя publish). В этом случае сообщение получат сразу несколько подписчиков. Каждый подписчик заведёт свою очередь сообщений, в которые «главная» очередь будет ложить копии сообщений.
Заключение
Очереди сообщений — классные. Иногда громоздкие, часто нетривиально настраиваемые, но всё равно приятные. Но никогда не стоит забывать, что MQ — это всё еще инструмент для решение конкретных задач, а не серебряная пуля или лекарство от рака. В каких-то задачах он полезен, в других — вызывает геморрой. В работе программиста гемора и так хватает, так что выбирайте осторожно.
Идеально было бы еще прочитать пару примеров do/don’t для всей этой кухни 🙂
Я бы с удовольствием, но в таких примерах полагается приводить случай из жизни, типа «а вот как-то в тайге…», а у меня таких нет. А истории, вычитанные у кого-то, не считаются.
Зато в следующий раз будет ZeroMQ и даже куски кода!
Павел. Интересный блог.
попал к вам случайно … а тут так много итересного…
есть вопросы готов меняться на пиво 🙂
можно как то с вами связаться?
За ранее спасибо .
Да, вполне можем пообщаться. Тут — http://dotsandbrackets.com/about-me/ — есть мои контакты. На сообщения в фэйсбуке я реагирую быстрее всего, в твиттер — медленнее (от слова «вечность»).
Publish-subscribe — публикация подписки)
Спасибо за статтью!
спасибо
Спасибо! Интересно и легко читать))
Рад стараться!