Плавная перезагрузка или остановка
Когда процесс сервера получает сигнал завершения (например, во время развёртывания или масштабирования), немедленное завершение прерывает все текущие запросы, оставляя клиентов с разорванными соединениями и потенциально повреждёнными операциями. Плавное завершение решает эту проблему:
- Завершение текущих запросов — Запросам, которые уже обрабатываются, даётся время на завершение, и клиенты получают корректные ответы вместо сброса соединений.
- Опустошение соединений — Сервер прекращает принимать новые соединения, в то время как существующие могут завершиться, предотвращая резкое отключение.
- Очистка ресурсов — Открытые подключения к базам данных, файловые дескрипторы и фоновые воркеры закрываются корректно, предотвращая повреждение данных или утечку ресурсов.
- Развёртывание без простоя — В сочетании с балансировщиком нагрузки плавное завершение позволяет развёртывать новые версии без видимых для пользователя ошибок.
Есть несколько способов достичь этого в Go.
Мы можем использовать fvbock/endless для замены стандартного ListenAndServe. Подробнее см. issue #296.
router := gin.Default()router.GET("/", handler)endless.ListenAndServe(":4242", router)Альтернативы endless:
- manners: Вежливый HTTP-сервер на Go с плавным завершением.
- graceful: Go-пакет для плавного завершения http.Handler сервера.
- grace: Плавный перезапуск и развёртывание без простоя для серверов на Go.
Если вы используете Go 1.8 и выше, вам может не понадобиться эта библиотека! Рассмотрите использование встроенного метода Shutdown() у http.Server для плавного завершения. См. полный пример graceful-shutdown с gin.
//go:build go1.8// +build go1.8
package main
import ( "context" "log" "net/http" "os" "os/signal" "syscall" "time"
"github.com/gin-gonic/gin")
func main() { router := gin.Default() router.GET("/", func(c *gin.Context) { time.Sleep(5 * time.Second) c.String(http.StatusOK, "Welcome Gin Server") })
srv := &http.Server{ Addr: ":8080", Handler: router.Handler(), }
go func() { // service connections if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatalf("listen: %s\n", err) } }()
// Wait for interrupt signal to gracefully shutdown the server with // a timeout of 5 seconds. quit := make(chan os.Signal, 1) // kill (no params) by default sends syscall.SIGTERM // kill -2 is syscall.SIGINT // kill -9 is syscall.SIGKILL but can't be caught, so don't need add it signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <-quit log.Println("Shutdown Server ...")
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if err := srv.Shutdown(ctx); err != nil { log.Println("Server Shutdown:", err) } log.Println("Server exiting")}