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 — база всегда закрывается последней, потому что создаётся первой. |