Привет, Хабр!

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


  1. alexdora
    24.06.2024 12:39
    +3

    Пакет go-micro 21k Stars на github. Но до сих пор я не видел кейсов применения где ты такой сидишь и думаешь: «ну маст хев»

    Не судите строго, наверное я чето не понимаю, или это попытка сделать из golang новый nodejs. Все эти задачи реализуются чуть-ли не стандартными пакетами.

    Pub/Sub - Zeromq, Protobuf тоже простой пакет…недавно буквально делал, ну это просто копи-паст. И список можно продолжать…


  1. qrKot
    24.06.2024 12:39

    Ну странное, в начале статьи пишем про главные фичи service discovery и load balancing, а потом не показываем, как они работают.

    Ну и в целом как-то очень похоже на ненужный комбайн, очередную попытку собрать в кучу уникальные реализации того, что и так уже есть в виде библиотек (иногда прямо в стандартной)