
Go благодаря возможностям компиляции и встроенным механизмам конкурентной многозадачности очень хорошо подходит для создания сетевых приложений и активно используется в создании инструментов для DevOps и распределенных приложений. В этой статье мы рассмотрим некоторые возможности фреймворка GoMicro для реализации микросервисных приложений на Go.
В микросервисной архитектуре одним из наиболее важных аспектов является механизм передачи данных внутри распределенного приложения и GoMicro поддерживает как использование REST, так и возможность применения очередей сообщений для обмена данных как в подходе RPC, так и для реализации архитектуры реактивных приложений, основанных на событиях. GoMicro основан на использовании расширяемой архитектуры и представляет большое количество важных подсистем для реализации микросервисов:
-
Аутентификация и авторизация обеспечивают для каждого сервиса идентификатор и сертификаты и реализует управление доступом на основе правил.
-
Хранилище данных — простой интерфейс хранилища данных для чтения, записи и удаления записей. Данные могут быть сохранены как в память, так и во внешнее хранилище.
-
Динамическая конфигурация — загрузка и обновление в реальном времени динамической конфигурации из любого места. Интерфейс конфигурации предоставляет способ загрузки конфигурации уровня приложения из любого источника, такого как переменные окружения, файл и т. д. Источники могут быть объединены и можно определить способ отката на другой источник или указать значения по умолчанию.
-
Механизм обнаружения сервисов — автоматическая регистрация сервисов и разрешение имен (по умолчанию на основе mDNS).
-
Балансировка нагрузки — балансировка нагрузки на стороне клиента, основанная на обнаружении сервисов. Нагрузка распределяется равномерно между несколькими реализациями сервисами с автоматическим переключением при наличии ошибок.
-
Кодирование сообщений — возможность кодирования сообщений в JSON / Protobuf.
-
Клиент/сервер RPC — запрос/ответ на основе RPC с поддержкой двунаправленной потоковой передачи.
-
Асинхронный обмен сообщениями — PubSub является основой для распределенных приложений, управляемых событиями (на основе HTTP-запросов).
-
Потоковая передача событий реализует поддержку потоков NATS Jetstream и Redis.
-
Синхронизация. Поддерживается распределенная блокировка и протокол динамического выбора лидера среди доступного кворума.
-
Подключаемые интерфейсы — Go Micro использует интерфейсы Go для каждой абстракции распределенной системы и позволяет создавать собственные реализации для каждого компонента.
Для подключения GoMicro нужно установить и импортировать модуль из «go-micro.dev/v4» и дальше использовать его методы для управления создаваемым сервисом. Также в модулях в github.com/go-micro/plugins/v4/… доступны плагины, которых делятся на следующие категории:
-
broker — взаимодействие с брокерами сообщений для реализации потоковой передачи сообщений (NATS, RabbitMQ, Kafka)
-
client — клиенты для запросов к другим микросервисам (через RPC, gRPC или HTTP)
-
server — серверный компонент для обеспечения доступа к микросервису (RPC, gRPC, HTTP)
-
codec — кодирование сообщений в BSON, Mercury, Protobuf
-
config — управление распределенной конфигурацией
-
registry — регистрация и обнаружение сервисов (в том числе, может взаимодействовать с Kubernetes)
-
selector — балансировка нагрузки
-
transport — двухсторонняя передача данных через NATS или RabbitMQ
-
wrapper — дополнительные middleware (например, логирование, трассировка запросов, ограничение скорости запросов и т.д.)
Создание сервиса начинается с вызова NewService и дальнейшей инициализации созданной структуры. Также сразу может быть выполнена регистрация сервиса и настройка способов обнаружения других сервисов, подключены серверные и клиентские компоненты.
package main import ( "go-micro.dev/v4" "github.com/go-micro/plugins/v4/registry/kubernetes" grpcc "github.com/go-micro/plugins/v4/client/grpc" grpcs "github.com/go-micro/plugins/v4/server/grpc" ) func main() { registry := kubernetes.NewRegistry() service := micro.NewService( micro.Name("my.service"), micro.Registry(registry), micro.Server(grpcs.NewServer()), micro.Client(grpcc.NewClient()), ) service.Server().handle() service.Init() service.Run() }
Далее для реализации обработчиков и вызовов удаленных методов могут использоваться механизмы кодогенерации для protobuf (protoc-gen-go), для доступа к зарегистрированным компонентам используются методы в service. Например, для получения доступа к объекту сервера можно обратиться к service.Server(), аналогично для подключения к сгенерированному клиенту service.Client() и т.д. При наличии proto-файла и сгенерированного описания методов и протокола взаимодействия с микросервисом, вызов может выглядеть следующим образом (в pb импортирован структура, сгенерированная protoc-gen-go)
if err := pb.RegisterPaymentServiceHandler(srv.Server(), new(handler.PaymentService)); err != nil { logger.Fatal(err) }
При этом сам proto-файл описывает все аспекты взаимодействия с микросервисом и определяет дополнительные типы данных (перечисления, структуры и др.), например:
syntax = "proto3"; package shop; option go_package = "./proto;shop"; service Health { rpc Check(HealthCheckRequest) returns (HealthCheckResponse) {} rpc Watch(HealthCheckRequest) returns (stream HealthCheckResponse) {} } message HealthCheckRequest { string service = 1; } message HealthCheckResponse { enum ServingStatus { UNKNOWN = 0; SERVING = 1; NOT_SERVING = 2; SERVICE_UNKNOWN = 3; } ServingStatus status = 1; }
При использовании реестров сервисов можно делать дополнительные обработки (например, создавать реализации селекторов для поиска подходящих сервисов). Также достаточно просто интегрируются очереди сообщений:
package main import ( "fmt" "context" "go-micro.dev/v4" ) func pub(i int, p micro.Publisher) { msg := &Message{ Say: fmt.Sprintf("This is an async message %d", i), } if err := p.Publish(context.TODO(), msg); err != nil { fmt.Println("pub err: ", err) return } fmt.Printf("Published %d: %v\n", i, msg) } func main() { service := micro.NewService() service.Init() p := micro.NewPublisher("example", service.Client()) for i := 0; i < 10; i++ { pub(i, p) } }
Аналогично можно создавать распределенные конфигурации, обнаруживать сервисы (через registry), создавать клиентов RPC, подписываться на появление сообщений в очередях RabbitMQ/Kafka. Также доступны механизмы для конфигурирования heartbeat (для проверки доступности и корректного функционирования сервисов):
service := micro.NewService( micro.Name("payment"), micro.RegisterInterval(time.Second*30), micro.RegisterTTL(time.Second*120), )
Дополнительно можно подключать собственные расширения с использованием micro.WrapHandler:
import ( "log" "go-micro.dev/v4" "go-micro.dev/v4/server" ) func logWrapper(fn server.HandlerFunc) server.HandlerFunc { return func(ctx context.Context, req server.Request, rsp interface{}) error { log.Printf("[request]: %v", req.Endpoint()) err := fn(ctx, req, rsp) return err } } func main() { service := micro.NewService( micro.Name("greeter"), // wrap the handler micro.WrapHandler(logWrapper), ) service.Init() proto.RegisterHandler(service.Server(), new(PaymentService)) if err := service.Run(); err != nil { fmt.Println(err) } }
Для мониторинга запущенных микросервисов может использоваться GoMicro Dashboard, который может быть установлен через go install github.com/go-micro/dashboard@latest.
Таким образом, MicroGo представляет легковесный и расширяемый фреймворк для разработки микросервисов, который обеспечивает разрабатываемые компоненты основными возможностями для безопасного доступа и обнаружения других микросервисов, хранением данных, взаимодействию через HTTP/gRPC или очереди сообщений и другими важными аспектами для реализации микросервисной архитектуры.
В завершение приглашаю на бесплатный урок, где мы рассмотрим плюсы и минусы монолитов и микросервисов, а также основные паттерны в микросервисной архитектуре.
ссылка на оригинал статьи https://habr.com/ru/company/otus/blog/718388/
Добавить комментарий