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