Итоговый проект: микросервис с Graceful Shutdown и БД
Мы подошли к финалу фундаментальной части обучения. Ранее мы разобрали, как упаковать приложение в Docker-контейнер и следовать принципам 12-факторных приложений. Теперь нам предстоит объединить все знания: работу с HTTP-сервером, подключение к PostgreSQL через pgx, использование контекста и горутин — чтобы создать отказоустойчивый микросервис.
Центральная тема этого этапа — Graceful Shutdown (плавное завершение работы). В React вы использовали функцию очистки (cleanup function) в useEffect, чтобы сбросить таймеры или отписаться от событий перед размонтированием компонента. В бэкенде на Go это критический процесс. Он гарантирует, что при обновлении сервиса запросы пользователей не оборвутся на середине, а соединения с базой данных закроются корректно.
Жизненный цикл процесса и системные сигналы
Когда микросервис запущен в контейнере, им управляет оркестратор (например, Kubernetes). Чтобы остановить сервис, операционная система посылает ему OS signals (системные сигналы).
Для нас важны два типа сигналов:
- SIGINT (Signal Interrupt) — отправляется при нажатии
Ctrl+Cв терминале. - SIGTERM (Signal Termination) — стандартный сигнал для остановки процесса в Docker и Kubernetes.
Если приложение не «слушает» эти сигналы, ОС принудительно завершит процесс через SIGKILL. В итоге активные HTTP-ответы не дойдут до клиента, а транзакции в базе данных могут «повиснуть».
Как показано в Схеме 1, корректный алгоритм завершения позволяет сервису доработать текущие задачи перед выходом.
Реализация: от теории к коду
В Go 1.22+ стандарт обработки сигналов — функция signal.NotifyContext. Она создает контекст, который переходит в состояние «отменен», как только в приложение прилетает нужный сигнал.
Мы запускаем HTTP-сервер в отдельной горутине. Это нужно, чтобы основная функция main могла заблокироваться и ждать сигнала отмены, не мешая серверу обрабатывать запросы.
package main
import (
"context"
"errors"
"log/slog"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/jackc/pgx/v5/pgxpool"
)
func main() {
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
// Создаем контекст, реагирующий на сигналы завершения
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
dbPool, err := pgxpool.New(ctx, "postgres://user:pass@localhost:5432/dbname")
if err != nil {
logger.Error("failed to connect to db", "error", err)
os.Exit(1)
}
defer dbPool.Close()
mux := http.NewServeMux()
mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
})
srv := &http.Server{
Addr: ":8080",
Handler: mux,
}
// Запускаем сервер в горутине
go func() {
logger.Info("starting server", "addr", srv.Addr)
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
logger.Error("listen and serve error", "error", err)
}
}()
// Ждем сигнала SIGINT или SIGTERM
<-ctx.Done()
logger.Info("shutting down gracefully...")
// Даем серверу 5 секунд на завершение текущих запросов
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := srv.Shutdown(shutdownCtx); err != nil {
logger.Error("server forced to shutdown", "error", err)
}
logger.Info("server stopped")
}Почему это важно для React-разработчика
Представьте: пользователь нажимает «Оплатить» в вашем интерфейсе. Бэкенд начинает транзакцию. В этот момент вы деплоите новую версию бэкенда.
- Без Graceful Shutdown: Процесс убивается мгновенно. Фронтенд получает
Network Error. Деньги могут быть списаны, но заказ в базе не создастся, так как код не успел дойти до конца. - С Graceful Shutdown: Сервер получает SIGTERM. Он перестает принимать новые соединения, но дожидается окончания обработки платежа и отправляет успешный ответ. Только после этого процесс завершается.
Использование os.Exit(0) сразу после получения сигнала. Это мгновенно убивает программу, игнорируя блоки defer и обрывая активные соединения.
Управление ресурсами базы данных
При завершении работы важно вызвать dbPool.Close(). Это закрывает пул соединений с PostgreSQL. Если этого не сделать, в базе останутся «мертвые» сессии, которые бесполезно потребляют память и лимиты подключений.
- Возьмите ваш текущий учебный проект.
- Реализуйте в нем
signal.NotifyContextдля обработки SIGTERM. - Добавьте в один из эндпоинтов задержку
time.Sleep(3 * time.Second). - Запустите сервер, сделайте запрос к этому эндпоинту и сразу нажмите
Ctrl+C. Проверьте по логам, что сервер дождался ответа и только потом закрылся.
Мы построили надежный скелет микросервиса. Это фундамент профессиональной разработки. Однако в крупных системах сервисам нужно не просто «уходить красиво», но и эффективно общаться друг с другом.
В следующей теме мы изучим gRPC для высокопроизводительного взаимодействия и разберем инструменты наблюдаемости (observability), чтобы видеть, что происходит внутри приложения.
Понравился урок?
Сохраните прогресс и получите персональный курс по любой теме — без форм и паролей
Продолжить в Telegram