В последнее время горячая новая модель архитектуры. Он прекрасно вписывается в докеризацию, 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.
Будем держать его в курсе.