Files
graceful-shutdown/README.md
T
2026-04-13 08:10:14 +03:00

97 lines
4.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## 👋 Привет! Я Олег Козырев
Staff Golang Engineer · Ex Ozon, Avito, Tinkoff
📺 [YouTube](https://www.youtube.com/@olezhek28go) · 💬 [Telegram](https://t.me/olezhek28go)
---
# 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 завис).
## Как попробовать
```bash
# Запуск
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 — база всегда закрывается последней, потому что создаётся первой.