Привет, Хабр!
Микросервисная архитектура представляет из себя подход, в котором каждый сервис отвечает за конкретную функциональность и может быть развернут, обновлен и масштабирован независимо от других. Go-Micro — это фреймворк, который упрощает создание таких микросервисов на Golang.
Основные фичи Go-Micro:
Автоматическое обнаружение сервисов: сервисы автоматом регистрируются и обнаруживаются.
Встроенная балансировка нагрузки: запросы равномерно распределяются между экземплярами сервисов.
Поддержка как синхронной, так и асинхронной коммуникации: возможность использования RPC и PubSub для обмена сообщениями между сервисами.
Гибкая конфигурация: динамическая загрузка и обновление конфигураций из различных источников.
Установим Go-Micro с помощью go get:
go get go-micro.dev/v4
Основные компоненты
Service является основой Go-Micro. Он предоставляет интерфейс для создания, инициализации и запуска микросервисов
service := micro.NewService( micro.Name("example"), micro.Version("latest"), ) service.Init() service.Run()
Registry отвечает за регистрацию и обнаружение сервисов. Он позволяет сервисам находить друг друга в распределенной системе. В Go-Micro реализована поддержка различных механизмов регистрации Consul, Etcd, Kubernetes и т.д. По дефолту используется встроенный механизм mDNS.
import "github.com/micro/go-micro/v2/registry/consul" registry := consul.NewRegistry() service := micro.NewService( micro.Registry(registry), )
Broker реализует паттерн Pub/Sub и отвечает за асинхронную передачу сообщений между сервисами. Он поддерживает различные брокеры сообщений: NATS, Kafka, RabbitMQ и другие.
import "github.com/micro/go-micro/v2/broker/nats" broker := nats.NewBroker() service := micro.NewService( micro.Broker(broker), )
Client предоставляет интерфейс для выполнения запросов к другим сервисам. Он поддерживает синхронные и асинхронные вызовы, автоматическую балансировку нагрузки и повторные попытки в случае неудачи. Клиент автоматом обнаруживает сервисы через
import "github.com/micro/go-micro/v2/client" req := client.NewRequest("example", "Example.Method", &Request{}) rsp := &Response{} err := client.Call(context.Background(), req, rsp)
Server обрабатывает входящие запросы и обеспечивает взаимодействие с клиентами. Он поддерживает протоколы HTTP и gRPC, и позволяет регистрировать обработчики запросов.
import "github.com/micro/go-micro/v2/server" server := micro.NewService( micro.Server(server.NewServer()), )
Transport отвечает за передачу данных между сервисами. Он поддерживает различные транспортные механизмы, включая HTTP, gRPC и т.п.
import "github.com/micro/go-micro/v2/transport/grpc" transport := grpc.NewTransport() service := micro.NewService( micro.Transport(transport), )
Codec предоставляет интерфейс для кодирования и декодирования сообщений. Он поддерживает различные форматы данных.
import "github.com/micro/go-micro/v2/codec/json" codec := json.NewCodec() service := micro.NewService( micro.Codec(codec), )
Создание микросервиса на примере
Создаем директорию проекта и инициализируем модуль Go:
mkdir order-service cd order-service go mod init order-service
Начнем с создания основного файла main.go, который будет запускать сам сервис:
package main import ( "log" "github.com/micro/go-micro/v2" "github.com/micro/go-micro/v2/server" "context" ) type OrderService struct{} func (s *OrderService) CreateOrder(ctx context.Context, req *CreateOrderRequest, rsp *CreateOrderResponse) error { // логика создания заказа rsp.OrderId = "12345" return nil } func main() { service := micro.NewService( micro.Name("order-service"), ) service.Init() server := service.Server() server.Handle( server.NewHandler(&OrderService{}), ) if err := service.Run(); err != nil { log.Fatal(err) } }
Для хранения заказов будем использовать базу данных PostgreSQL. Начнем с установки драйвера psql:
go get github.com/lib/pq
Добавим взаимодействие с БД в сервис:
package main import ( "database/sql" "log" "github.com/micro/go-micro/v2" "github.com/micro/go-micro/v2/server" _ "github.com/lib/pq" "context" ) type OrderService struct { db *sql.DB } func (s *OrderService) CreateOrder(ctx context.Context, req *CreateOrderRequest, rsp *CreateOrderResponse) error { query := "INSERT INTO orders (product_id, quantity) VALUES ($1, $2) RETURNING id" err := s.db.QueryRow(query, req.ProductId, req.Quantity).Scan(&rsp.OrderId) if err != nil { return err } return nil } func main() { db, err := sql.Open("postgres", "user=username password=password dbname=orderdb sslmode=disable") if err != nil { log.Fatal(err) } service := micro.NewService( micro.Name("order-service"), ) service.Init() orderService := &OrderService{db: db} server := service.Server() server.Handle( server.NewHandler(orderService), ) if err := service.Run(); err != nil { log.Fatal(err) } }
Для балансировки нагрузки будем юзать Consul как сервисный регистр и NGINX для распределения запросов. Устанавливаем Consul и настраиваем его для регистрации наших сервисов.
brew install consul consul agent -dev
Настраиваем сервис для использования Consul:
import ( "github.com/micro/go-micro/v2/registry" "github.com/micro/go-micro/v2/registry/consul" ) func main() { consulReg := consul.NewRegistry(registry.Addrs("127.0.0.1:8500")) service := micro.NewService( micro.Name("order-service"), micro.Registry(consulReg), ) service.Init() orderService := &OrderService{db: db} server := service.Server() server.Handle( server.NewHandler(orderService), ) if err := service.Run(); err != nil { log.Fatal(err) } }
Теперь настроим NGINX для балансировки нагрузки. Создаем файл конфигурации NGINX /etc/nginx/nginx.conf:
http { upstream order_service { least_conn; server 127.0.0.1:8080; server 127.0.0.1:8081; } server { listen 80; location / { proxy_pass http://order_service; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } }
Перезапускаем NGINX:
sudo systemctl restart nginx
Теперь сервис заказов готов к использованию. Можно запускать несколько экземпляров сервиса на разных портах и NGINX будет балансировать нагрузку между ними. Запустим два экземпляра сервиса:
go run main.go -server_address=:8080 go run main.go -server_address=:8081
Теперь любой запрос к сервису будет балансироваться между этими двумя экземплярами.
Подробнее с другими возможностями Go-Micro можно ознакомиться здесь.
В завершение напомню, что сегодня вечером в Otus пройдёт открытый урок, посвященный теме модульных монолитов и DDD. На нём участники рассмотрят основы domain-driven
design и применение к предметно-ориентированному проектированию. Обсудят, как DDD помогает в построении архитектуры. Если актуально, регистрируйтесь по ссылке.
