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 или очереди сообщений и другими важными аспектами для реализации микросервисной архитектуры.
Когда микросервисы появляются не ради модного слова, а из-за реальной нагрузки на продукт, быстро выясняется: мало просто разделить систему на сервисы. Нужно понимать границы контекстов, способы взаимодействия, отказоустойчивость, брокеры сообщений и последствия каждого архитектурного решения.
Если сейчас вы как раз разбираетесь, когда монолит уже мешает, а микросервисы ещё могут добавить хаоса, присмотритесь к курсу OTUS «Микросервисная архитектура». Перед стартом можно пройти вступительное тестирование: оно поможет оценить текущий уровень и понять, какие темы стоит подтянуть заранее.
