Привет, Хабр!
Микросервисная архитектура представляет из себя подход, в котором каждый сервис отвечает за конкретную функциональность и может быть развернут, обновлен и масштабирован независимо от других. 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 помогает в построении архитектуры. Если актуально, регистрируйтесь по ссылке.
Комментарии (2)
qrKot
24.06.2024 12:39Ну странное, в начале статьи пишем про главные фичи service discovery и load balancing, а потом не показываем, как они работают.
Ну и в целом как-то очень похоже на ненужный комбайн, очередную попытку собрать в кучу уникальные реализации того, что и так уже есть в виде библиотек (иногда прямо в стандартной)
alexdora
Пакет go-micro 21k Stars на github. Но до сих пор я не видел кейсов применения где ты такой сидишь и думаешь: «ну маст хев»
Не судите строго, наверное я чето не понимаю, или это попытка сделать из golang новый nodejs. Все эти задачи реализуются чуть-ли не стандартными пакетами.
Pub/Sub - Zeromq, Protobuf тоже простой пакет…недавно буквально делал, ну это просто копи-паст. И список можно продолжать…