97 lines
4.3 KiB
Markdown
97 lines
4.3 KiB
Markdown
|
|
## 👋 Привет! Я Олег Козырев
|
|||
|
|
|
|||
|
|
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 — база всегда закрывается последней, потому что создаётся первой.
|