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

Что такое микросервисы?

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

Благодаря тому, что каждая служба может быть отдельным приложением, это позволяет более легко и точно масштабировать операции. Компоненту А нужно столько-то экземпляров с таким количеством ЦП и ОЗУ. Компонент B, однако, используется реже, и для его правильного функционирования требуются только базовые ресурсы.

Еще один аспект - безопасность: не все микросервисы должны быть общедоступными, и даже более того, при подключении API микросервисов к Front-end существует несколько шаблонов.

«Микро» не означает, что сама служба должна иметь X-строчку кода или быть небольшой. Мы также не должны разбивать наш код на слишком много сервисов, это похоже на правило DRY, но с некоторой заботой о себе в будущем.

Например, предположим, что у нас есть монолитный интернет-магазин. Это монолит с некоторыми модулями / классами разделения логики, такими как: user management, permission account control list (acl) и некоторой другой бизнес-логикой, такой как calculate income, shopping cart или search.

Зачем иметь столько в одном месте?
Ну, можно сказать, разрабатывать наверняка легче, но с ограниченным составом команды. Опять же, для масштабируемых целей каждый модуль может иметь свою собственную команду, или команда A может обрабатывать модули X, Y, Z, в то время как команда B обрабатывает V, U, M.

Хорошо, это разделение кода, но как насчет данных?

Поскольку логика кодирования может быть разделена, структура базы данных также может быть разделена. Хорошая практика - иметь Database per Service, если база данных вообще требуется!

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

Еще один плюс в том, что у нас может быть несколько типов баз данных, которые лучше всего подходят для конкретной службы. Например, для службы A это может быть Non-SQL, а для другого - PostgreSQL.

Покажи мне код, чувак

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

Обнаружение службы

Для этого нам понадобится сервис с шаблоном Service Discovery. В облаке часто Load-Balancer может обнаруживать экземпляры самостоятельно (в AWS это, например, Target Group, которое может быть подключено к ALB и обнаруживает вновь созданные ECS Tasks).

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

Как видите, нет необходимости делать Service Discovery доступным из Интернета, поскольку он обращается только к внутренним ресурсам.

В этом direct pattern все API микросервисов видны из Интернета и из Frontend. Последний также решает, к какому микросервису направить запрос на консолидацию данных.

Вернемся к Service Discovery. Как это работает?
Я придумал что-то вроде этого:

Во-первых, это переменная реестра внутренней памяти. Это список, хранит {URL, PORT} под service name as a KEY.

Чтобы зарегистрировать новую переменную, просто запустите POST /register {name, port, host} endpoint.

Но как предотвратить отображение unactive служб в registry?
Ну, один из способов - создать /unregister конечную точку при отправке SIGTERM.
Другой метод - через healthcheck; поэтому у нас есть второй поток помимо Flask. Каждые 15 секунд он проверяет, работает ли /health конечная точка в службах. Если не может подключиться к нему, то удаляет услугу из набора.

Простая реализация:

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

2. Пользовательское обслуживание

Еще одна услуга, которую я реализовал, - это User Service. По сути, это CRUD, который сохраняет данные в файловое хранилище, но он должен подключаться к Service Discovery, чтобы другие службы знали, что он существует.

И код:

Так что ничего необычного, простого, базового CRUD: файл обновлений, удаляет файл с помощью запросов:

# CREATE
curl -d '{"age":"25","name":"George","permission_level":"1"}' -X POST  -H "Content-Type: application/json" http://127.0.0.1:3000/user/george
# RETRIEVE
curl -X GET http://127.0.0.1:3000/user/george
# UPDATE
curl -d '{"age":"26"}' -X PUT  -H "Content-Type: application/json" http://127.0.0.1:3000/user/george
# DELETE
curl -X DELETE http://127.0.0.1:3000/user/george

Служба тестирования и обнаружение

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

Все идет нормально ; service_discovery получил /register конечную точку, а user_service получил /health конечную точку. Посмотрим, что видит service_discovery:

Потрясающие! Давайте протестируем CRUD-запросы:

Как вы можете видеть, запросы CRUD работают, и кроме нескольких /health запросов конечных точек от discovery_service он получил должным образом другие запросы, которые были созданы и обновлены пользователем Джордж.

Теперь давайте добавим еще один микросервис - разрешения.

Служба разрешений

Этот немного сложнее. Он подключается не только к обнаружению служб, но и к user_service.

У него есть только одна конечная точка (вы можете продлить ее, если хотите) для изменения уровня разрешений у пользователя и некоторой проверки данных, опять же довольно простая услуга:

Посмотрим, подключается ли он к службе обнаружения. Спойлер: да :)

Теперь наш discovery_service выглядит так:

>>> curl localhost:2999/registry
<<<
{"message":{
     "permissions_service":["127.0.0.1","3001"],
     "user_service":["127.0.0.1","3000"]
},"status":"OK"}

Я знаю, что это можно сделать лучше, и так должно быть. Настоящее обнаружение должно иметь возможность иметь несколько экземпляров службы с заданным идентификатором и с использованием некоторого load-balancing алгоритма, такого как round-robin , и возвращать наименее используемый экземпляр службы.
Это, однако, всего лишь пример. :)

Следующий шаг - проверить, может ли permission_service подключиться к user_service через discovery_service для обновления данных.

Итак, приступим: curl -d ‘{“permission_level”: “3”}’ -X PUT -H “Content-Type: application/json” http://127.0.0.1:3001/set_permission/george

Итак, произошло несколько вещей.
Сначала мы сделали запрос PUT к permission_service.
Затем permission_service спросил discovery_service: «Эй, где я могу найти user_service, чтобы я мог задать ему один крошечный вопрос?»
И, наконец, после discovery_service ответа наша исходная служба выполнила запрос и обновила пользователя Джорджа:

Как видите, permission_level для Джорджа увеличено с 1 до 3.

Что происходит, когда одна служба отключается?

Что ж, решать тебе. Я решил опустить эту проверку в permission_service, но вы можете добавить простой try:...except:..., чтобы перехватить неудачный запрос и обработать его через него.

Что касается discover_service, когда проверка работоспособности прекращается, она удаляет службу из реестра и возвращает 404 при поиске данного микросервиса.

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

Резюме

Это был действительно простой пример создания API с использованием микросервисов. Как я уже сказал, если вы работаете в облаке, правильно настроенная инфраструктура сделает это за вас.
В следующей статье я постараюсь объяснить вам закономерности подключения микросервисного API к Frontend.

Будем держать его в курсе.