Ответ
Graceful Shutdown (плавное завершение) — это процесс корректного завершения работы приложения, который позволяет завершить текущие операции, закрыть соединения и освободить ресурсы перед полной остановкой. Это критически важно, чтобы не терять данные и не оставлять незавершенных транзакций.
Основные шаги реализации:
- Создать контекст для управления жизненным циклом: Используется
context.WithCancel
. - Настроить прослушивание системных сигналов: Создается канал для сигналов
os.Signal
, который будет ловитьsyscall.SIGINT
(Ctrl+C) иsyscall.SIGTERM
(сигнал завершения от systemd, Docker, Kubernetes). - Запустить горутину-слушателя: Эта горутина блокируется до получения сигнала. При получении она вызывает
cancel()
для созданного контекста. - Ожидать завершения: Основная горутина блокируется до тех пор, пока контекст не будет отменен (
<-ctx.Done()
). - Выполнить очистку: После получения сигнала о завершении вызываются методы для плавной остановки (например,
http.Server.Shutdown
) и закрытия других ресурсов (БД, брокеры сообщений).
Пример для HTTP-сервера:
func main() {
// 1. Создаем контекст и канал для сигналов
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
srv := &http.Server{
Addr: ":8080",
Handler: http.DefaultServeMux,
}
// 2. Запускаем горутину, которая ждет сигнал и отменяет контекст
go func() {
<-sigCh
log.Println("Shutdown signal received, initiating graceful shutdown...")
cancel()
}()
// 3. Запускаем сервер в отдельной горутине
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("Could not listen on %s: %vn", srv.Addr, err)
}
}()
log.Println("Server is ready to handle requests.")
// 4. Блокируемся, пока не будет вызван cancel()
<-ctx.Done()
// 5. Создаем контекст с таймаутом для шатдауна
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 15*time.Second)
defer shutdownCancel()
// Выполняем плавное завершение
if err := srv.Shutdown(shutdownCtx); err != nil {
log.Fatalf("Server shutdown failed: %v", err)
}
// Здесь можно добавить закрытие других ресурсов, например, БД
// db.Close()
log.Println("Server gracefully stopped.")
}
Ключевые моменты:
srv.Shutdown()
плавно останавливает сервер, не прерывая активные соединения. Он ждет их завершения.context.WithTimeout
вShutdown
гарантирует, что приложение не будет висеть вечно, если какие-то соединения не могут завершиться.