Files
2026-04-13 08:10:14 +03:00

4.3 KiB
Raw Permalink Blame History

👋 Привет! Я Олег Козырев

Staff Golang Engineer · Ex Ozon, Avito, Tinkoff

📺 YouTube · 💬 Telegram


Graceful Shutdown Demo

Учебный Go-проект, демонстрирующий паттерн graceful shutdown — корректное завершение приложения с дожиданием текущих запросов и освобождением ресурсов.

Что внутри

Полноценное HTTP-приложение с несколькими слоями:

  • HTTP API — три эндпоинта: /health, /users/me, /slow
  • Сервисы — бизнес-логика (UserService, AuthService, NotificationService)
  • Репозитории — слой данных (UserRepo, SessionRepo, NotificationRepo)
  • Инфраструктура — база данных (PostgreSQL), кэш (Redis), шина событий (EventBus)
  • DI-контейнер — ленивая инициализация зависимостей с автоматической регистрацией в closer
  • Closer — менеджер graceful shutdown (LIFO-порядок закрытия ресурсов)

Как работает graceful shutdown

  1. signal.NotifyContext перехватывает SIGINT (Ctrl+C) и SIGTERM (Kubernetes)
  2. server.Shutdown() — перестаёт принимать новые соединения и дожидает текущие запросы (таймаут 15с)
  3. closer.CloseAll() — закрывает все ресурсы в обратном порядке: кэш, затем БД (таймаут 10с)
  4. Суммарный бюджет: 15с + 10с = 25с из 30с Kubernetes grace period

Паттерн «двойной Ctrl+C»: первый — graceful shutdown, второй — мгновенное завершение (для разработки, если shutdown завис).

Как попробовать

# Запуск
go run cmd/main.go

# В другом терминале — медленный запрос
curl localhost:8080/slow

# Пока запрос висит — нажмите Ctrl+C в терминале сервера
# Без graceful shutdown: curl получит "connection reset by peer"
# С graceful shutdown: curl дождётся ответа "готово!" — 200 OK

Структура проекта

cmd/
  main.go                  # Точка входа
internal/
  app/
    app.go                 # Жизненный цикл приложения, graceful shutdown
    di.go                  # DI-контейнер с ленивой инициализацией
  api/
    server.go              # HTTP-хендлеры и маршрутизация
  closer/
    closer.go              # Менеджер закрытия ресурсов (LIFO)
  config/
    config.go              # Конфигурация (DSN, Redis, HTTP-порт)
  database/
    database.go            # Абстракция базы данных
  cache/
    cache.go               # Абстракция кэша (Redis)
  events/
    bus.go                 # Шина событий (pub/sub)
  service/
    user.go                # Бизнес-логика пользователей
    auth.go                # Авторизация
    notification.go        # Уведомления
  repository/
    user.go                # Доступ к данным пользователей
    session.go             # Доступ к данным сессий
    notification.go        # Доступ к данным уведомлений

Граф зависимостей

Handler
├── UserService
│   ├── UserRepo → DB
│   ├── AuthService
│   │   ├── UserRepo → DB
│   │   ├── SessionRepo → DB + Cache
│   │   ├── Cache
│   │   └── EventBus
│   └── EventBus
├── AuthService
└── NotificationService
    ├── NotificationRepo → DB
    ├── UserService
    └── EventBus

Порядок закрытия (LIFO): EventBus → Cache → DB — база всегда закрывается последней, потому что создаётся первой.