kapo4ka 07 ноября 2019

По стопам лучших: микросервисная архитектура в разрезе

Использовать микросервисную архитектуру или нет? Разбираемся с плюсами и минусами, внутренней коммуникацией, принципами и заблуждениями.
По стопам лучших: микросервисная архитектура в разрезе

Виды архитектуры

Простейший и популярный вариант архитектуры – монолитная. Каждый начинал с неё, и здесь нет никакой изоляции и распределённости: один монолит обрабатывает все запросы.

По стопам лучших: микросервисная архитектура в разрезе

Отчего возникают следующие проблемы:

  • отказоустойчивость;
  • горизонтальное масштабирование;
  • применение одной технологии или языка и невыгодность переписывать огромный монолит;
  • сложность рефакторинга из-за хранения кода в одном месте и куча legacy;
  • трудности работы в команде разработчиков;
  • чтобы использовать повторно, придётся дробить.

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

По стопам лучших: микросервисная архитектура в разрезе

Это частично решает проблемы отказоустойчивости, масштабируемости и одного стека технологий.

Микросервисная архитектура – не новая идея, а разновидность сервис-ориентированной архитектуры. Сервис-ориентированная архитектура предусматривает модульность разработки и слабую связанность компонентов, поэтому получаем изолированную и распределённую систему.

По стопам лучших: микросервисная архитектура в разрезе

Главный минус – общая шина данных Enterprise Service Bus с огромными спецификациями и сложностями работы с абстракциями и фасадами.

Микросервисная архитектура

Микросервисная архитектура наследует от предшественницы изоляцию и распределённость. Здесь база данных не используется как шина данных, за исключением отдельных случаев в пользу производительности. По классической схеме компоненты изолируются и на уровне кода, и на уровне базы.

По стопам лучших: микросервисная архитектура в разрезе

Для микросервисов применяют контейнеризацию с оркестрацией и другими плюшками.

Следующее преимущество – протоколы обнаружения сервисов. Оцените наглядно разницу коммуникаций сервис-ориентированной и микросервисной архитектуры: у последней нет общей шины, и сервис обращается к любому другому напрямую:

По стопам лучших: микросервисная архитектура в разрезе

Выбор протоколов общения зависит от программиста. Например, вы используете REST для публичных запросов и RPC через AMQP для внутренних либо один общий протокол для всех.

По стопам лучших: микросервисная архитектура в разрезе

Разделяют микросервисы с точки зрения либо бизнеса, либо программиста для переиспользования. Но мешают этому две вещи:

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

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

По стопам лучших: микросервисная архитектура в разрезе

Достоинства и недостатки микросервисной архитектуры

Как в любой распределённой архитектуре, получим накладные расходы на коммуникацию.

Концепция непрерывной интеграции и доставки (CI/CD) и построение архитектуры (контейнеризация, оркестрация, мониторинг и другое) требует большого количества времени.

Что насчёт отказоустойчивости? Часто её определяют как «падение одного сервиса не отражается других». Представьте, падает Audit, а Wallet теоретически продолжает работать – похоже на отказоустойчивость:

По стопам лучших: микросервисная архитектура в разрезе

А как же запросы по RPC, которые Wallet продолжает слать? Необходимо программно предусмотреть ситуацию, когда Audit не отвечает, и грамотно настроить rollback, поскольку базы разные, и транзакционно это сделать не получится.

По стопам лучших: микросервисная архитектура в разрезе

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

По стопам лучших: микросервисная архитектура в разрезе

Масштабируемость у микросервисов достигается благодаря системам оркестрации, ведь остаётся достаточно серверных ресурсов в противоположность монолиту, который потреблял всю память и процессор.

Стандартный процесс разработки – кодинг, тестирование и развёртывание – в микросервисной архитектуре выглядит иначе. Первые два этапа сливаются, поскольку микросервис взаимодействует с кучей других. Чтобы локально сделать хоть один запрос, придётся запустить все эти микросервисы, поэтому тестирование вручную не подходит для подобной задачи.

По стопам лучших: микросервисная архитектура в разрезе

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

Будете выполнять столько запросов вручную? Добавьте ещё сложности взаимодействия микросервисов и получите ад.

Поэтому локально разработчик проводит юнит-тестирование, где вместо ответов микросервисов будут mock-объекты. Ещё понадобятся функциональные тесты, например, для отлавливания проблем коммуникации, а также интеграционные тесты. Они прогоняются вместе с юнит-тестами на этапе слияния рабочий копий в главную ветку разработки. И только потом программист проверяет функциональность руками. До развёртывания релизную версию тестирует QA.

По стопам лучших: микросервисная архитектура в разрезе

Микросервисная архитектура делает компоненты независимыми при разработке и развёртывании, чего не было в монолите.

Микросервисы используются повторно и экономят средства в плане бизнеса.

Программист получает относительную свободу в выборе языка и технологий для разработки отдельных частей проекта.

Контроль зависимостей

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

По стопам лучших: микросервисная архитектура в разрезе

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

Базы данных

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

Вот возможные решения проблемы:

  • храните одно и то же значение в двух микросервисах, но появляются трудности с актуализацией данных;
  • делайте RPC, правда, усложните работу с большими объёмами информации;
  • выгрузите данные из всех баз для аналитики;
  • сделайте миграцию данных, что тоже непросто и повлечёт написание RPC.

Представьте другой случай: при регистрации создаётся 3 сущности (пользователь, профиль и счёт), но на полпути что-то падает. Как откатить изменения, если данные распределяются по разным микросервисам? Думайте об этом на этапе проектирования.

Внутренняя коммуникация в микросервисной архитектуре

Для общения микросервисам нужен контракт: протокол и валидация данных. С последним справляется JSON Schema, но протокол также необходим. Требования при выборе способа коммуникации:

  • строгость;
  • общий протокол для сервера и клиента;
  • генерация кода для любого языка;
  • производительность.

В качестве протоколов используют Protocol Buffers, FlatBuffers, Apache Thrift. Сначала вы пишете предметно-ориентированный язык, отдаёте это программе-генератору кода и получаете сгенерированный клиент и сервер.

По стопам лучших: микросервисная архитектура в разрезе

Организация работы в команде

Команды делят по технологиям и следят за их размерами (не более 7–8 человек). Как правило, задачи выдают сеньорам, рядом с которыми хватает разработчиков уровнем ниже.

К примеру, два сеньора из одной команды получили по два микросервиса. Важно, чтобы программисты взаимодействовали между собой не только в локальной группе по конкретной задаче, но и с другими. Тогда в области их знаний будет много общего:

  • язык;
  • организация и шаблонизация кода для каждого микросервиса;
  • библиотеки;
  • концепция непрерывной интеграции и доставки;
  • протокол коммуникации;
  • документация.

А что объединит разнородные по языкам команды? Останется только CI/CD, глобально стандартизованный прокол и подход к документированию.

Устройство микросервисов

Микросервисы состоят из трёх слоёв: небольших обработчиков, бизнес-логики и мапперов данных. Последовательность выполнения запроса выглядит так:

По стопам лучших: микросервисная архитектура в разрезе

В сервисном слое сосредотачивается 99% всего кода. Поскольку в микросервисе несколько обработчиков, используйте Data Transfer Object (DTO), к которому вы будете приводить GET-запрос. Это облегчает обработку и валидацию.

Примеры кода

Не забывайте, что Node.js – асинхронный язык. Рассмотрим пример: приходит запрос, синхронно берёт HolyJSService, асинхронно работает с маппером, выполняющим запись в базу данных, и процесс повторяется для последующих запросов. Никакого порядка в этом не наблюдается, поэтому выделяют два архитектурных подхода:

  • stateless – не хранить состояние;
  • stateful – добавлять объекту класса поля, которые нужны для выполнения запроса.

Когда используете stateful, при получении запроса вы создаёте новый объект HolyjSService и заполняете необходимыми для выполнения данными. Дальше этот сервис асинхронно идёт в базу данных. Следующий запрос берёт новый экземпляр сервиса, и процесс повторяется.

По стопам лучших: микросервисная архитектура в разрезе

В коде вы реализовываете абстрактный класс с кучей полей и сеттеров, наследуете от него новый сервис и наполняете методами.

По стопам лучших: микросервисная архитектура в разрезе

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

По стопам лучших: микросервисная архитектура в разрезе

При stateless-подходе используется контекст, который создаётся для каждого запроса отдельно, и единственный экземпляр сервиса, поскольку выполнение запросов от него не зависит.

По стопам лучших: микросервисная архитектура в разрезе

В контексте хранятся все необходимые поля и собственно метод для его создания:

По стопам лучших: микросервисная архитектура в разрезе

На уровне сервиса выглядит как дополнительный параметр:

По стопам лучших: микросервисная архитектура в разрезе

Для обработчика добавляем контекст вторым параметром – его возвращает метод createContextFromHttpRequest:

По стопам лучших: микросервисная архитектура в разрезе

Сами по себе хэндлеры предельно простые:

По стопам лучших: микросервисная архитектура в разрезе

Поскольку для выполнения запросов часто нужны другие сервисы, контекст приходится прокидывать в них. Допустим, на первый микросервис поступил запрос. Присваивайте ему идентификатор X-Request-Id, с которым он пройдётся по всем микросервисам. Благодаря логированию вы легко отследите путь внешнего запроса. X-Trace-Id используется для обозначения единой бизнес-операции, состоящей из нескольких запросов.

Для создания идентификаторов используют OpenTracing.

По стопам лучших: микросервисная архитектура в разрезе

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

По стопам лучших: микросервисная архитектура в разрезе

Теперь отдельные микросервисы используют созданный сервис и общий интерфейс.

По стопам лучших: микросервисная архитектура в разрезе

Если команде, которая пишет на другом языке, также понадобился RBAC, придётся написать для них дополнительный RPC. Однако не получится создать RPC для добавления фильтров или условий к управлению доступом.

Заблуждения

1. Это легче

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

По стопам лучших: микросервисная архитектура в разрезе

С самого начала вы медленно и печально разбираетесь с документацией, бесконечно подбираете протоколы и другие компоненты. К энному микросервису вы нарабатываете стандартные шаблоны, что в несколько раз облегчает процесс.

С точки зрения поддержки – наоборот, потому что увеличивается количество микросервисов. Новый разработчик вынужден будет часами сидеть за изучением диаграмм, чтобы понять глобальную картину и принцип работы системы.

2. Лучше производительность

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

Заключение

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

А какой тип архитектуры используете вы?

Источники

МЕРОПРИЯТИЯ

Комментарии

ВАКАНСИИ

Добавить вакансию
Разработчик С#
от 200000 RUB до 400000 RUB
Go-разработчик
по итогам собеседования
Senior Java Developer
Москва, по итогам собеседования

ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ