Thank you for reading this post, don't forget to subscribe!
Так как контейнеры изолированы друг от друга, то выбор транспорта для сообщений сильно ограничен, и, скорее всего, это будет сеть и 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 — это всё еще инструмент для решение конкретных задач, а не серебряная пуля или лекарство от рака. В каких-то задачах он полезен, в других — вызывает геморрой.