Go: Микросервисы

15 вопросов

1 Архитектура микросервисов. Основные принципы.

Сервисы - небольшие, развертываются независимо, владеют своими данными, общаются по сети (HTTP/gRPC, очереди). В Go каждый сервис - отдельное приложение (бинарник), свой репозиторий или монорепо. Конфигурация и обнаружение сервисов выносятся наружу (Consul, etcd, K8s). Тестирование - контракты, моки, интеграционные тесты.

Открыть отдельно →
2 Монолит и микросервисы. Когда дробить на Go?

Монолит проще разрабатывать и деплоить на старте. Микросервисы дают независимый масштаб и деплой, технологическое разнообразие. Дробить имеет смысл при разных нагрузках на части, разных командах или при необходимости изоляции сбоев. В Go монолит часто строят модульно (пакеты, интерфейсы), чтобы потом вынести сервис без переписывания.

Открыть отдельно →
3 Синхронная и асинхронная коммуникация между сервисами в Go.

Синхронная: HTTP/gRPC вызовы, ожидание ответа. Простота, но связность и каскадные сбои. Асинхронная: очереди (Kafka, RabbitMQ), события; слабая связность, буферизация. В Go синхронно - http.Client или gRPC client; асинхронно - продюсер/консьюмер Kafka или AMQP. Для надежности и снятия пиков часто комбинируют: команды по HTTP, события по очереди.

resp, err := httpClient.Post(ctx, "http://orders/create", "application/json", body)
// или
producer.Produce(ctx, &kafka.Message{TopicPartition: tp, Value: payload})
Открыть отдельно →
4 Паттерн Saga в микросервисах. Компенсирующие транзакции в Go.

Saga - распределенная транзакция через цепочку локальных транзакций; при сбое выполняют компенсирующие действия (откат). Хореография: каждый сервис публикует события, следующие реагируют и при ошибке публикуют компенсацию. Оркестрация: центральный оркестратор вызывает сервисы и при ошибке запускает компенсации. В Go реализуют как конечный автомат или слой оркестратора с вызовами сервисов и откатом.

Открыть отдельно →
5 Circuit Breaker в Go. Реализация и библиотеки.

Circuit breaker предотвращает каскадные сбои: при большом числе ошибок переходит в состояние "open" и не вызывает бэкенд (сразу возвращает ошибку или fallback); после таймаута пробует снова (half-open). В Go: sony/gobreaker, hashicorp/go-circuitbreaker. Оборачивают вызовы внешнего сервиса; при открытом контуре не делают запрос. Метрики (число вызовов, ошибок) для мониторинга.

cb := gobreaker.NewCircuitBreaker(st)
result, err := cb.Execute(func() (interface{}, error) {
    return client.Call(ctx, req)
})
Открыть отдельно →
6 Transaction Outbox в Go. Зачем и как реализовать.

Outbox - запись бизнес-события и сообщения для публикации в одну транзакцию в БД; отдельный процесс читает outbox и публикует в очередь. Гарантирует доставку при сбое после commit. В Go: таблица outbox (id, aggregate_id, payload, created_at, published); в той же транзакции INSERT в outbox; воркер SELECT неопубликованных, публикует в Kafka/RabbitMQ, помечает опубликованными.

tx.ExecContext(ctx, "INSERT INTO orders ...")
tx.ExecContext(ctx, "INSERT INTO outbox (topic, payload) VALUES ($1, $2)", "order.created", json)
Открыть отдельно →
7 Event Sourcing в контексте Go.

Event Sourcing - состояние хранится как лог событий; текущее состояние восстанавливается применением событий. В Go: хранилище событий (таблица или Kafka), агрегат применяет события (Apply(event)); при запросе загружают события и пересчитывают или хранят снапшоты. Подходит для аудита и воспроизведения. Сложнее запросы и консистентность; часто комбинируют с CQRS (отдельная read-модель).

Открыть отдельно →
8 Service Discovery в Go. Consul, etcd, K8s.

Сервисы регистрируются при старте и получают адреса других сервисов по имени. Consul/etcd: регистрация с TTL, здоровье через HTTP check; клиент резолвит имя в список инстансов. В K8s - DNS (имя сервиса резолвится в ClusterIP). В Go: клиент Consul (hashicorp/consul), etcd (etcd-client); при вызове другого сервиса берут адрес из discovery, балансируют (round-robin).

// K8s: http.Get("http://orders-service:8080/health")
// Consul: services, _ := consul.Client().Health().Service("orders", "", true, nil)
Открыть отдельно →
9 API Gateway и Go. Роль и реализация.

API Gateway - единая точка входа: маршрутизация по пути/хосту к бэкендам, аутентификация, rate limiting, агрегация ответов. В Go можно реализовать gateway как HTTP-сервер с reverse proxy (httputil.ReverseProxy) и middleware (auth, limit). Или использовать готовые (Kong, Envoy) и конфигурировать. Для простых сценариев достаточно Nginx/Envoy; кастом на Go - при сложной логике маршрутизации и агрегации.

proxy := httputil.NewSingleHostReverseProxy(backendURL)
proxy.Director = func(r *http.Request) {
    r.URL.Host = backendURL.Host
    r.URL.Scheme = backendURL.Scheme
}
http.Handle("/api/", authMiddleware(proxy))
Открыть отдельно →
10 BFF (Backend for Frontend) в Go.

BFF - отдельный бэкенд под конкретный клиент (веб, мобильное приложение), агрегирует вызовы к микросервисам и адаптирует ответы. В Go BFF - HTTP-сервер: принимает запрос от клиента, вызывает несколько сервисов (параллельно где возможно), собирает ответ в нужном формате. Уменьшает число запросов с клиента и скрывает детали внутренних API.

func (b *BFF) GetDashboard(ctx context.Context, userID string) (*Dashboard, error) {
    var orders *Orders
    var profile *Profile
    g, ctx := errgroup.WithContext(ctx)
    g.Go(func() error { orders, _ = b.orders.GetByUser(ctx, userID); return nil })
    g.Go(func() error { profile, _ = b.profile.Get(ctx, userID); return nil })
    if err := g.Wait(); err != nil { return nil, err }
    return &Dashboard{Orders: orders, Profile: profile}, nil
}
Открыть отдельно →
11 Backpressure в потоковой обработке. Реализация в Go.

Backpressure - замедление производителя при перегрузке потребителя, чтобы не накапливать необработанные сообщения. В Go: ограничение числа горутин-воркеров (пул), каналы с буфером ограниченного размера, семантики с блокировкой при полном буфере. При чтении из Kafka ограничивают параллелизм обработки и commit только после обработки. Паттерн: worker pool с N воркерами, задачами по каналу.

sem := make(chan struct{}, 10)
for msg := range kafkaMessages {
    sem <- struct{}{}
    go func(m Message) {
        defer func() { <-sem }()
        process(m)
    }(msg)
}
Открыть отдельно →
12 Graceful degradation в Go.

При недоступности части системы приложение снижает функциональность, но продолжает работать. В Go: проверка доступности зависимостей (health), при сбое возвращать заглушки или кешированные данные; circuit breaker не вызывать сломанный сервис. Критичные операции - быстрый отказ с понятной ошибкой; некритичные - fallback (пустой список, кеш). Мониторинг и алерты по деградации.

if !cache.IsHealthy() {
    return nil, ErrServiceUnavailable
}
data, err := cache.Get(ctx, key)
if err != nil {
    return getFromDB(ctx, key) // fallback
}
Открыть отдельно →
13 Retry и exponential backoff в Go при вызовах сервисов.

При временной ошибке (сеть, 5xx) повторять запрос с задержкой. Exponential backoff: задержка растет (1s, 2s, 4s, ...) до лимита. В Go: цикл с проверкой контекста и ошибки (retryable или нет), time.Sleep(backoff), ограничение числа попыток. Библиотеки: cenkalti/backoff, hashicorp/go-retryablehttp. Не ретраить идемпотентность-критичные операции без идемпотентного ключа или только безопасные методы (GET).

for i := 0; i < maxRetries; i++ {
    resp, err := client.Do(req)
    if err == nil && resp.StatusCode < 500 { return resp, nil }
    select {
    case <-ctx.Done(): return nil, ctx.Err()
    case <-time.After(time.Duration(1<
Открыть отдельно →
14 Health checks в микросервисе на Go.

Liveness - процесс жив (простой endpoint или отсутствие дедлока). Readiness - готов принимать трафик (БД доступна, зависимости отвечают). В Go: /health возвращает 200 или 503; проверки - пинг БД, вызов зависимостей с таймаутом. В K8s liveness/readiness probes указывают на эти endpoint. Отдельный endpoint для зависимостей (dependency health) для отладки.

func (h *Health) Readiness(w http.ResponseWriter, r *http.Request) {
    if err := h.db.PingContext(r.Context()); err != nil {
        w.WriteHeader(503)
        return
    }
    w.WriteHeader(200)
}
Открыть отдельно →
15 Dead Letter Queue (DLQ) при обработке сообщений в Go.

Сообщения, которые не удалось обработать после N попыток, отправляют в DLQ для разбора и ручной обработки. В Go: при ошибке обработки не коммитить offset (или nack); после N ретраев публиковать сообщение в отдельный топик/очередь (DLQ). Отдельный воркер или оператор обрабатывает DLQ (логи, алерты, повторная публикация после исправления).

if err := process(msg); err != nil {
    if attempt >= maxAttempts {
        dlqProducer.Produce(ctx, msg)
        return
    }
    return err
}
consumer.Commit(msg)
Открыть отдельно →