Расскажите про ваш опыт разработки микросервисов на Go.

Ответ

Я активно занимался проектированием и разработкой бэкенда на основе микросервисной архитектуры с использованием Go.

Типичный стек технологий, с которым я работал:

  • Транспорт: gRPC для синхронного межсервисного взаимодействия (RPC) и REST для внешних API. gRPC предпочтительнее для внутренних коммуникаций из-за строгой типизации (Protobuf) и высокой производительности.
  • Асинхронное взаимодействие: Kafka или RabbitMQ для обмена сообщениями между сервисами, реализации паттернов Event Sourcing и CQRS.
  • Хранилища данных: PostgreSQL для реляционных данных, Redis для кэширования и быстрых данных, ClickHouse для аналитики.
  • Оркестрация и деплой: Docker для контейнеризации, Kubernetes для управления контейнерами, Helm для пакетирования приложений и GitLab CI/CD или ArgoCD для автоматизации сборки и развертывания.

Ключевые архитектурные практики и паттерны:

  1. Наблюдаемость (Observability):
    • Логирование: Структурированное логирование (например, с zerolog или slog) для удобного парсинга.
    • Метрики: Сбор метрик приложения (запросы в секунду, время ответа, ошибки) с помощью Prometheus и визуализация в Grafana.
    • Трассировка: Распределенная трассировка с OpenTelemetry и экспорт данных в Jaeger или Tempo для анализа полного пути запроса через несколько сервисов.
  2. Отказоустойчивость: Реализация паттерна Circuit Breaker (например, с gobreaker) для предотвращения каскадных сбоев.
  3. Конфигурация: Централизованное управление конфигурацией с помощью переменных окружения и библиотек типа Viper.
  4. Health Checks: Реализация эндпоинтов для проверки состояния сервиса, что необходимо для Kubernetes (livenessProbe, readinessProbe).

Пример gRPC Health Check:

// Используем стандартный протокол проверки состояния gRPC
import (
    "google.golang.org/grpc"
    "google.golang.org/grpc/health"
    healthpb "google.golang.org/grpc/health/grpc_health_v1"
)

func main() {
    // ... инициализация сервера ...
    server := grpc.NewServer()
    healthServer := health.NewServer()

    // Регистрируем сервис проверки состояния
    healthpb.RegisterHealthServer(server, healthServer)

    // Устанавливаем статус для нашего сервиса
    // Пустая строка означает статус всего сервера
    healthServer.SetServingStatus("", healthpb.HealthCheckResponse_SERVING)

    // ... запуск сервера ...
}

Основные вызовы, с которыми я сталкивался:

  • Согласованность данных: Решение проблем распределенных транзакций с помощью паттерна Saga.
  • Сложность отладки: Использование распределенной трассировки для выявления узких мест и ошибок.
  • Service Discovery: Настройка обнаружения сервисов в Kubernetes.
  • Определение границ сервисов: Правильное разделение монолита на логически независимые и слабосвязанные сервисы.

Ответ 18+ 🔞

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

Слушай, а ты в курсе, что эта вся твоя микросервисная архитектура — она как бабушкин сундук? Там дохуя всего накидано, и если не знать, где что лежит, можно на старый утюг наступить. Ну ладно, поехали.

Вот на чём я, сука, обычно катаюсь:

  • Чтобы сервисы друг с другом трепались: gRPC — это наш бронепоезд для внутренних разговоров. Быстро, строго, по протоколу. А для чужих дядек снаружи — REST, пусть по старой доброй HTTP-шке стучатся.
  • Чтобы не ждать друг друга как лохи: Kafka или RabbitMQ. Кинул событие в очередь и пошёл дальше пить кофе. Идеально для всяких хитрожопых схем вроде Event Sourcing.
  • Куда данные складывать: PostgreSQL — для всего серьёзного, где связи и транзакции. Redis — чтобы всё летало, как угорелое. А ClickHouse — когда нужно посчитать, сколько раз твою APIшку дернули за прошлый квартал.
  • Как всю эту банду запустить и не сойти с ума: Docker, Kubernetes, Helm. Без этого — пиши пропало, будешь как дурак сервера вручную настраивать.

А теперь про самое важное — как не облажаться и всё контролировать:

  1. Наблюдаемость (Observability): Это святое, блядь. Без этого ты слепой крот в подземелье.

    • Логи: Пишем не абы как, а структурированно, чтобы потом не рыться в них, как свинья в апельсинах.
    • Метрики: Prometheus + Grafana. Чтобы видеть, не лег ли какой-нибудь сервис костьми, пока ты спал.
    • Трассировка: OpenTelemetry в дело. Чтобы когда всё ебнулось, можно было проследить весь путь запроса и понять, кто же из этих пид... э-э-э, сервисов, виноват.
  2. Отказоустойчивость: Ставим Circuit Breaker (автоматический выключатель, как в щитке). Один сервис загнулся — остальные не должны за ним, как дураки, в тартарары лететь.

  3. Конфигурация: Не раскидываем настройки по всем углам, а храним централизованно. Viper в помощь.

  4. Health Checks: Обязательно делаем эндпоинты, по которым кубернетес может потыкать сервис палкой и понять, жив он или уже в лучшем из миров.

Вот, смотри, как для gRPC сервиса здоровье проверить:

import (
    "google.golang.org/grpc"
    "google.golang.org/grpc/health"
    healthpb "google.golang.org/grpc/health/grpc_health_v1"
)

func main() {
    server := grpc.NewServer()
    healthServer := health.NewServer()

    healthpb.RegisterHealthServer(server, healthServer)
    healthServer.SetServingStatus("", healthpb.HealthCheckResponse_SERVING)
}

И главное, с какими подводными еб... камнями сталкиваешься:

  • Согласованность данных в распределёнке: Ох, ёбушки-воробушки. Один сервис обновил данные, а второй про это не узнал. Тут паттерн Saga выручает, но головной боли тоже добавляет.
  • Отладка: Когда запрос гуляет по десяти сервисам, найти, где он сдох — это тот ещё квест. Спасает только трассировка, иначе пиши пропало.
  • Service Discovery в Kubernetes: Чтобы сервисы друг друга находили в этом овердохуище подов.
  • Границы сервисов: Вот это, блядь, самое сложное! Как от монолита отпилить кусок, чтобы получился независимый сервис, а не кусок говна на блюдечке. Неправильно нарезал — получишь дикую связность и хуй потом что переделаешь.

Короче, весело, блядь. Но когда всё настроено и работает — красота.