Микросервис

Материал из Гостопедии
Перейти к навигации Перейти к поиску

Микросервисы — независимо выпускаемые сервисы, которые моделируются вокруг предметной области бизнеса[1]. Микросервисом называют один из таких сервисов, рассматривая его среди аналогичных сервисов в составе системы, имеющей микросервисную архитектуру.

В этой статье микросервис описан с точки зрения задач документирования.

Зачем нужны микросервисы?

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

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

Таким образом, микросервис всегда предназначен для выполнения некоторого небольшого набора связанных по смыслу задач. «Упаковка» задач в микросервисы позволяет наращивать производительность их выполнения по мере необходимости, а не дорогостоящими рывками.

Функционирование микросервиса

Начнем с примера

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

Функцию допуска зрителя в кинозал можно разложить на задачи и сгруппировать их в микросервисы, как показано ниже.

Microser-tickets-01.png

Микросервис Задачи
ticket-reader-daemon
  • Считывание предъявленного билета
pass-controller
  • Проверка предъявленного билета
turnstile-controller
  • Разблокирование прохода в кинозал
ticket-crud
  • Чтение данных о билете из БД
  • Изменение данных о билете в БД

Теперь опишем возможный алгоритм функционирования системы при выполнении этой функции.

  1. Микросервис ticket-reader-daemon запускает проверку билета.
    1. Определяет момент, когда билет оказывается перед объективом видеокамеры.
    2. Распознает указанный на билете номер.
    3. Отправляет запрос микросервису pass-controller.
  2. Микросервис pass-controller проверяет билет.
    1. Запрашивает у микросервиса ticket-crud данные билета по его номеру.
    2. Если ticket-crud возвращает код ошибки, то завершает задачу.
    3. Если билет помечен как использованный, то завершает задачу.
    4. Направляет микросервису turnstile-controller запрос.
  3. Микросервис turnstile-controller позволяет зрителю войти в кинозал.
    1. Открывает турникет.
    2. Закрывает турникет после того, как зритель прошел через него.
    3. Закрывает турникет по по тайм-ауту, если зритель вспомнил, что забыл выключить дома утюг, и убежал.
    4. Возвращает микросервису pass-controller результат.
  4. Микросервис pass-controller помечает билет как использованный.
    1. Проверяет результат: прошел или не прошел зритель через турникет.
    2. Если турникет был закрыт по тайм-ауту, то завершает задачу
    3. Направляет микросервису ticket-crud запрос на обновление данных билета.

Режимы выполнения задач

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

  • по запросу;
  • автоматически.

Выполнение задачи по запросу

Клиент и сервер в контексте запроса

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

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

  1. Простаивает в ожидании очередного запроса.
  2. Получает запрос от клиента.
  3. Выполняет предусмотренную запросом задачу.
  4. Отправляет результат выполнения задачи клиенту.
  5. Возвращается к шагу 1.

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

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

Архитектурный принцип REST

Как именно должно быть выстроено взаимодействие компонентов системы друг с другом? Во избежание путаницы хотелось бы придать их поведению единый стиль. Популярный способ добиться этого — использование архитектурного принципа REST.

Архитектурный принцип Representational state transfer (REST) полностью освобождает сервер от обязанности хранить контекст взаимодействия со своими клиентами. Целостность контекста при выполнении в системе многошаговых процессов каждый клиент обеспечивает себе сам. Отправляя другому компоненту системы запрос, клиент включает туда все данные, необходимые серверу для его выполнения. Это могут быть данные о состоянии самого клиента, о текущем шаге выполнения какой-то высокоуровневой задачи и т. п.

Образно говоря, сервер выполняет запросы, поступающие от одного и того же клиента, всякие раз, как в первый. Оправляя запрос, клиент не может рассчитывать на способность сервера «помнить» хоть что-то о его предыдущих запросах.

Интерфейс микросервиса

Использование протокола HTTP/HTTPS

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

URL микросервиса

Протокол HTTP требует, чтобы клиент обращался к серверу по определенному URL, поэтому свой URL должен быть у каждого микросервиса в системе. В технической документации обычно указывают не полный URL микросервиса, а ту его часть, которая следует за доменным именем или IP-адресом. Например, для микросервиса ticket-crud может быть указан URL /acinema/v1/ticket-crud.

Методы микросервиса и их энд-пойнты

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

Разработчики микросервиса могут сопоставить каждому методу микросервиса продолжение пути, которое нужно присоединить к основному URL этого микросервиса при оправке ему запроса, чтобы вызвать этот метод. Получаемые таким образом URL называют энд-пойнтами. Например, у микросервиса pass-controller могут быть энд-пойнты /acinema/v1/pass-controller/verify и /acinema/v1/pass-controller/invalidate, первый вызывает метод проверки предъявленного билета, а второй — метод пометки использованного билета.

Входные данные

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

  • включение входных данных параметров в путь;
  • указание входных данных в параметрах URL;
  • включение входных данных в тело запроса.

Первые два способа представляют собой синтаксически разные способы указания параметров. При включении в путь их имена и значения становятся как бы именами файлов и директорий. Например, для того чтобы запросить у микросервиса ticket-crud данные билета номер 12345, мы вызовем его по URL /acinema/v1/ticket-crud/read/ticket/12345, а чтобы удалить этот билет — по URL /acinema/v1/ticket-crud/delete/ticket/12345. Разработчики любят этот способ указания параметров за лаконичность и внятность: складывается впечатление, что мы «спускаемся» в созданную где-то на сервере директорию tickets и находим в ней файл билета 12345, в реальности же мы присвоили параметру ticket значение 12345. Если бы разработчики этого микросервиса решили принимать входные данные в параметрах URL, то аналогичные вызовы имели бы вид /acinema/v1/ticket-crud/read?ticket=12345 и /acinema/v1/ticket-crud/delete?ticket=12345.

Входные данные, имеющие значительный объем и ли сложную структуру, как правило, включают в тело запроса. Для представления таких данных используют структурированные форматы, чаще JSON, реже XML. Использование других текстовых форматов возможно технически, но практикуется без особых причин. Например, входные данные, отправляемые микросервису ticket-crud для обновления билета, могут выглядеть, как показано ниже.

{
   "ticketStatus": "used",
   "whereUsed": "г. Царевококошайск, ул. Гоголя, д. 12",
   "whenUser": "2024-12-24 20:54:12"
}

Методы HTTP

Протокол HTTP предусматривает ряд так называемых методов[2], которые клиент обязательно использует при отправке запросов серверу. Метод отражает семантику запроса. Технически от метода зависит порядок обработки запроса серверным программным обеспечением. Кроме того, каждый метод допускает ли не допускает наличие тела в запросе и в ответе на него.

В технической документации для каждого метода микросервиса следует указывать не только его энд-пойнт, но и метод HTTP, используемый при обращении к нему. Например, фрагмент описания микросервиса ticket-crud мог бы выглядеть следующим образом.

Чтение данных о билете из базы данных

GET /acinema/v1/ticket-crud/read/tickets/{ticket_number}

(Приведено описание метода.)

Обновление данных о билете в базе данных

GET /acinema/v1/ticket-crud/update/tickets/{ticket_number}

(Приведено описание метода.)

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

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

Выходные данные

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

  • код состояния HTTP (обязательно);
  • выходные данные (если предусмотрены задачей).

Коды состояния[3] предусмотрены протоколом HTTP. Код состояния показывает, успешно ли выполнен запрос, а если нет, то какого рода ошибка возникла.

Выходные данные должны быть включены в тело ответа. Для представления выходных данных, имеющих значительный объем и сложную структуру обычно используют формат JSON или реже XML. Использование других текстовых форматов тоже возможно, если для такого решения находятся достаточные основания. Например, микросервис ticket-crud мог бы возвращать данные о билете в следующем формате.

{
   "ticketNumber": "12345",
   "ticketStatus": "valid",
   "whereUsed": null,
   "whenUser": null
}

Режимы выполнения запросов

Известны следующие режимы выполнения запросов:

  • синхронный;
  • асинхронный.

Эти режимы различаются поведением клиента после отправки запроса серверу.

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

Syncromode.png

В асинхронном режиме клиент завершает выполнение собственной задачи после отправки запроса. Сервер выполняет полученный запрос и отправляет ответ клиенту. Клиент получает этот ответ и запускает собственную задачу по его обработке. Между отправкой запроса серверу и получением ответа от него клиент сохраняет способность к выполнению других задач. Последнее особенно важно, если с ним взаимодействует пользователь, которого раздражали бы «подвисания» клиента.

Asyncmode.png

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

Режим выполнения запроса обычно он определяется реализацией клиента, а не сервера. Поэтому его следует указывать при описании порядка выполнения задачи клиентом. Например, в описание порядка работы фронт-энда может быть включено следующее утверждение.

Фронт-энд направляет запрос на получение списка страховых компаний микросервису insurance-companies-crud в асинхронном режиме.

Автоматическое выполнение задачи

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

Микросервисы, выполняющие задачи в автоматическом режиме, принято называть демонами.

Тип микросервиса

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

Тип Характер решаемых задач Примеры выполняемых микросервисами задач
CRUD Доступ к хранящейся в БД информации об экземплярах определенной сущности предметной области. Микросервис обеспечивает создание, чтение, обновление и удаление данных. Функциональность микросервиса должна быть ограничена этим набором задач, возможно, с разными вариантами их выполнения. Название типа представляет собой акроним от английских глаголов create, read, update, delete Создание, чтение, обновление и удаление данных о застрахованном автомобиле
Viewer Чтение хранящейся в БД информации об экземплярах определенной сущности предметной области. Необходимость в микросервисе с такой функциональностью может возникнуть, если, например, считываемые им импортируются в систему извне Чтение данных об удостоверении личности владельца застрахованного автомобиля
Reporter Сбор или агрегация данных об удовлетворяющих определенным условиям экземплярах сущностей предметной области Получение списка всех автомобилей, принадлежащих заданному владельцу
Calculator Выполнение тех или иных нетривиальных вычислений Расчет стоимости страхового полиса по данным об автомобиле его владельце
Controller Выполнение многошаговой задачи, требующей следования определенному алгоритму и запуска других задач Выполнение очередного шага заключения договора с корпоративным страхователем
Processor Обработка определенного блока или массива данных Распознавание марки автомобиля по его фотографии
Daemon Автоматическое выполнение определенных задач по расписанию Рассылка владельцам автомобилей предложений о продлении страховых полисов

Слой микросервиса

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

Слой Обозначение Допустимые источники запросов
Back to front B2F Фронт-энд и, возможно, другие микросервисы
Back to back B2B Другие микросервисы

Часть требований к микросервису может зависеть от его слоя. Например, на уровне всего проекта требования к защищенности микросервисов слоя B2F от хакерских атак могут быть более строгими.

Состав микросервиса

Типичен следующий состав компонентов микросервиса:

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

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

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

База данных должна входить в состав микросервиса, если он только не предназначен для решения чисто вычислительных задач. Микросервисная архитектура предполагает автономность микросервисов, а значит, каждый микросервис должен «все свое носить собой», в том числе данные. Если несколько микросервисов в системе нуждаются в одних и тех же данных, например, в справочниках, то они должны создать у себя их копии, а в системе должен быть налажен механизм их своевременного обновления. Разработчики, которые считают такой подход чересчур радикальным и обременительным, могут вынести базу данных на уровень функционального модуля, включающего в себя несколько микросервисов.

Клиентские библиотеки микросервиса нужны программистам, которые обращаются к нему из своих компонентов системы. Они помогают сократить объем рутинной работы по написанию кода, выполняющего подготовку входных данных, разбор выходных данных, отправку запросов микросервису и обработку возможных ошибок. Состав клиентских библиотек зависит от того, какие компоненты системы пользуются микросервисом, и на каких языках программирования они написаны. Например, если микросервис обслуживает другие микросервисы, написанные на языке Python, и фронт-энд, то разработчики могут подготовить для своих коллег две клиентские библиотеки: одну для языка Python, и одну для языка JavaScript.

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

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

Источники

  1. Ньюмен С. Создание микросервисов. 2-е изд. — СПб.: Питер, 2023. — 624 с.: ил. — (Серия «Бестселлеры O’Reilly»). ISBN 978-5-4461-1145-9.