Поделиться через


Обмен данными в архитектуре микрослужб

Подсказка

Это фрагмент из электронной книги «Архитектура микрослужб .NET для контейнеризованных приложений .NET», доступной в документации .NET или в виде бесплатного скачиваемого PDF-файла, который можно прочитать в автономном режиме.

Архитектура микросервисов .NET для приложений .NET в контейнерах, миниатюра обложки электронной книги.

В монолитном приложении, работающем в одном процессе, компоненты вызывают друг друга с помощью метода или вызовов функций на уровне языка. Они могут быть строго связаны, если вы создаете объекты с помощью кода (например, new ClassName()), или могут быть вызваны в разобщённом формате, если вы применяете внедрение зависимостей, ссылаясь на абстракции, а не конкретные экземпляры объектов. В любом случае объекты работают в одном процессе. Самая большая проблема при переходе с монолитного приложения на приложение на основе микрослужб заключается в изменении механизма взаимодействия. Прямое преобразование вызовов метода в вызовы RPC к службам приведет к частому и неэффективному обмену данными, который не будет хорошо работать в распределённых средах. Проблемы проектирования распределенных систем достаточно хорошо известны, поэтому существует даже канон, известный как заблуждения распределенных вычислений, перечисляющий предположения, которые разработчики часто делают при переходе от монолитных к распределенным проектам.

Существует не одно решение, но несколько. Одним из решений является изоляция микрослужб бизнеса по возможности. Затем вы используете асинхронное взаимодействие между внутренними микрослужбами и заменяете детализированное взаимодействие, характерное для внутрипроцессной коммуникации между объектами, на более укрупнённое взаимодействие. Это можно сделать, группируя вызовы и возвращая данные, агрегируя результаты нескольких внутренних вызовов клиенту.

Приложение на основе микрослужб — это распределенная система, работающая на нескольких процессах или службах, как правило, даже на нескольких серверах или узлах. Каждый экземпляр службы обычно является процессом. Таким образом, службы должны взаимодействовать с использованием протокола обмена данными между процессами, таких как HTTP, AMQP или двоичный протокол, например TCP, в зависимости от характера каждой службы.

Сообщество микрослужб способствует философии "умных конечных точек и глупых труб". Этот лозунг поощряет дизайн, который как можно более разделен между микрослужбами, и как можно более сплоченный в рамках одной микрослужбы. Как описано ранее, каждая микрослужба владеет собственными данными и собственной логикой домена. Но микрослужбы, составляющие комплексное приложение, обычно просто управляются с помощью взаимодействия REST, вместо сложных протоколов, таких как WS-* и используют гибкие коммуникации на основе событий, вместо централизованных оркестраторов бизнес-процессов.

Два часто используемых протокола — HTTP-запрос/ответ с API-интерфейсами ресурсов (при запросе в большинстве случаев), а также упрощённый асинхронный обмен сообщениями при обмене обновлениями между несколькими микросервисами. Более подробно описаны в следующих разделах.

Типы связи

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

Первая ось определяет, является ли протокол синхронным или асинхронным:

  • Синхронный протокол. HTTP — это синхронный протокол. Клиент отправляет запрос и ожидает ответа от службы. Это независимо от выполнения клиентского кода, который может выполняться синхронно (с блокировкой потока) или асинхронно (без блокировки потока, и в конечном итоге ответ будет передан в обратный вызов). Важно отметить, что протокол (HTTP/HTTPS) синхронен, и клиентский код может продолжать задачу только при получении ответа HTTP-сервера.

  • Асинхронный протокол. Другие протоколы, такие как AMQP (протокол, поддерживаемый многими операционными системами и облачными средами), используют асинхронные сообщения. Код клиента или отправитель сообщений обычно не ожидает ответа. Он просто отправляет сообщение, как при отправке сообщения в очередь RabbitMQ или любого другого брокера сообщений.

Вторая ось определяет, имеет ли связь один приемник или несколько приемников:

  • Один приемник. Каждый запрос должен обрабатываться ровно одним получателем или службой. Примером этого взаимодействия является шаблон команды.

  • Несколько получателей. Каждый запрос может обрабатываться одним или несколькими получателями. Этот тип связи должен быть асинхронным. Примером является механизм публикации и подписки , используемый в шаблонах, таких как архитектура на основе событий. "Это основано на интерфейсе шины событий или брокере сообщений для распространения обновлений данных между несколькими микрослужбами через события. В общем случае это реализуется через шину сервисов или аналогичный артефакт, например Azure Service Bus, с использованием тем и подписок."

Приложение на основе микрослужб часто использует сочетание этих стилей связи. Наиболее распространенным типом является взаимодействие с одним приемником с синхронным протоколом, например HTTP/HTTPS при вызове обычной службы HTTP веб-API. Микрослужбы также обычно используют протоколы обмена сообщениями для асинхронного взаимодействия между микрослужбами.

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

Асинхронная интеграция микрослужбы обеспечивает автономию микрослужбы

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

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

Кроме того, наличие зависимостей HTTP между микрослужбами, например при создании длительных циклов запросов и ответов с цепочками HTTP-запросов, как показано на первой части рис. 4-15, не только делает микрослужбы не автономными, но и их производительность влияет, как только одна из служб в этой цепочке не работает хорошо.

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

Схема, показывающая три типа связи между микрослужбами.

Рис. 4-15. Антипаттерны и паттерны во взаимодействии между микросервисами

Как показано на приведенной выше схеме, при синхронном взаимодействии создается "цепочка" запросов между микрослужбами при обслуживании клиентского запроса. Это антипаттерн. В микрослужбах асинхронного взаимодействия используются асинхронные сообщения или http-опрос для взаимодействия с другими микрослужбами, но запрос клиента обслуживается сразу.

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

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

Как отмечалось ранее в разделе "Определение границ модели домена для каждого микросервиса", дублирование некоторых данных в нескольких микросервисах не является неправильным решением. Наоборот, таким образом вы можете перевести данные на язык или термины этого дополнительного домена или ограниченного контекста. Например, в приложении eShopOnContainers у вас есть микрослужба identity-api, которая отвечает за большинство данных пользователя с сущностью, названной User. Однако если необходимо хранить данные о пользователе в микрослужбе Ordering , вы сохраняете его как другую сущность с именем Buyer. Сущность Buyer имеет ту же идентичность, что и исходная User сущность, но может иметь только несколько атрибутов, необходимых для домена Ordering, а не всего профиля пользователя.

Вы можете использовать любой протокол для асинхронного обмена данными между микрослужбами, чтобы обеспечить конечную согласованность. Как уже упоминалось, можно использовать события интеграции с помощью шины событий или брокера сообщений или даже использовать HTTP, опрашив другие службы. Это неважно. Важное правило заключается в том, чтобы не создавать синхронные зависимости между микрослужбами.

В следующих разделах объясняется несколько стилей взаимодействия, которые можно использовать в приложении на основе микрослужб.

Стили взаимодействия

Существует множество протоколов и вариантов, которые можно использовать для обмена данными, в зависимости от типа связи, который вы хотите использовать. Если вы используете синхронный механизм обмена данными на основе запросов и ответов, такие протоколы, как HTTP и REST, являются наиболее распространенными, особенно если вы публикуете службы за пределами узла Docker или кластера микрослужб. Если вы взаимодействуете между службами внутри (в узле Docker или кластере микрослужб), вы также можете использовать механизмы обмена данными в двоичном формате (например, WCF с использованием TCP и двоичного формата). Кроме того, можно использовать асинхронные механизмы связи на основе сообщений, такие как AMQP.

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

Обмен данными по запросу и ответу с HTTP и REST

Когда клиент использует связь с запросом и ответом, он отправляет запрос в службу, затем служба обрабатывает запрос и отправляет ответ обратно. Взаимодействие с запросами и ответами особенно хорошо подходит для запроса данных для пользовательского интерфейса в режиме реального времени (динамический пользовательский интерфейс) из клиентских приложений. Поэтому в архитектуре микрослужб вы, вероятно, будете использовать этот механизм связи для большинства запросов, как показано на рис. 4-16.

Схема, на которой показаны запросы и ответы в режиме реального времени для оперативных запросов и обновлений.

Рис. 4-16. Использование взаимодействия HTTP-запроса и ответа (синхронное или асинхронное)

Если клиент использует обмен данными с запросами и ответами, он предполагает, что ответ будет поступать в течение короткого времени, как правило, менее секунды или в течение нескольких секунд. Для отложенных ответов необходимо реализовать асинхронное взаимодействие на основе шаблонов обмена сообщениями и технологий обмена сообщениями, который является другим подходом, который мы объясним в следующем разделе.

Популярный стиль архитектуры для обмена данными о запросах и ответах — REST. Этот подход основан на протоколе HTTP и тесно связан с протоколом HTTP , охватывая http-команды, такие как GET, POST и PUT. REST — это наиболее часто используемый подход к архитектуре при создании служб. При разработке ASP.NET основных веб-API можно реализовать службы REST.

При использовании служб REST HTTP в качестве языка определения интерфейса существует дополнительное значение. Например, если вы используете метаданные Swagger для описания API вашего сервиса, можно использовать инструменты, которые генерируют клиентские заглушки, способные напрямую обнаруживать и потреблять ваши сервисы.

Дополнительные ресурсы

Отправка и обмен данными в режиме реального времени на основе HTTP

Другая возможность (обычно для других целей, отличных от REST), — это взаимодействие в режиме реального времени и одно ко многим с более высоким уровнем платформ, таких как ASP.NET SignalR и протоколы, такие как WebSockets.

Как показано на рисунке 4-17, обмен данными HTTP в режиме реального времени означает, что вы можете отправлять содержимое сервера в подключенные клиенты по мере того, как данные становятся доступными, а не ждать, пока клиент будет запрашивать новые данные.

Схема push-уведомлений и коммуникаций в режиме реального времени на основе SignalR.

Рис. 4-17. Асинхронная передача сообщений в режиме реального времени для связи одного с многими

SignalR — это хороший способ обеспечить взаимодействие в режиме реального времени для отправки содержимого клиентам с внутреннего сервера. Так как обмен данными находится в режиме реального времени, клиентские приложения отображают изменения почти мгновенно. Обычно это обрабатывается протоколом, таким как WebSockets, с помощью многих подключений WebSockets (по одному на клиент). Типичный пример — это когда служба сообщает об изменении счёта спортивной игры многим клиентским веб-приложениям одновременно.