go http server优雅关闭Shutdown方法
go 1.24.0
用法
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
mx := http.NewServeMux()
mx.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("ok\n"))
})
srv := http.Server{
Addr: ":8001",
Handler: mx,
}
go func() {
if err := srv.ListenAndServe(); err != nil {
panic(err)
}
}()
signalCh := make(chan os.Signal, 1)
signal.Notify(signalCh, syscall.SIGINT, syscall.SIGTERM)
<-signalCh
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatalf("Server shutdown failed: %v\n", err)
}
log.Println("Server shutdown gracefully")
}
原理
// Shutdown gracefully shuts down the server without interrupting any
// active connections. Shutdown works by first closing all open
// listeners, then closing all idle connections, and then waiting
// indefinitely for connections to return to idle and then shut down.
// If the provided context expires before the shutdown is complete,
// Shutdown returns the context's error, otherwise it returns any
// error returned from closing the [Server]'s underlying Listener(s).
//
// When Shutdown is called, [Serve], [ListenAndServe], and
// [ListenAndServeTLS] immediately return [ErrServerClosed]. Make sure the
// program doesn't exit and waits instead for Shutdown to return.
//
// Shutdown does not attempt to close nor wait for hijacked
// connections such as WebSockets. The caller of Shutdown should
// separately notify such long-lived connections of shutdown and wait
// for them to close, if desired. See [Server.RegisterOnShutdown] for a way to
// register shutdown notification functions.
//
// Once Shutdown has been called on a server, it may not be reused;
// future calls to methods such as Serve will return ErrServerClosed.
func (s *Server) Shutdown(ctx context.Context) error {
s.inShutdown.Store(true)
s.mu.Lock()
lnerr := s.closeListenersLocked()
for _, f := range s.onShutdown {
go f()
}
s.mu.Unlock()
s.listenerGroup.Wait()
pollIntervalBase := time.Millisecond
nextPollInterval := func() time.Duration {
// Add 10% jitter.
interval := pollIntervalBase + time.Duration(rand.Intn(int(pollIntervalBase/10)))
// Double and clamp for next time.
pollIntervalBase *= 2
if pollIntervalBase > shutdownPollIntervalMax {
pollIntervalBase = shutdownPollIntervalMax
}
return interval
}
timer := time.NewTimer(nextPollInterval())
defer timer.Stop()
for {
if s.closeIdleConns() {
return lnerr
}
select {
case <-ctx.Done():
return ctx.Err()
case <-timer.C:
timer.Reset(nextPollInterval())
}
}
}
1. 触发所有已注册的关闭回调。
2. 关闭所有监听器。
3. 一直等待活跃连接变空闲,然后关闭空闲连接,除非context超时。
一旦Shutdown方法被调用了,继续调用Serve/ListenAndServe/ListenAndServeTLS/Shutdown方法,会返回ErrServerClosed。
Shutdown方法不会等待或者结束被劫持的http连接,例如websocket连接,因为websocket会劫持http底层的tcp连接,http层面已感知不到。RegisterOnShutdown方法可以注册处理方式。
浙公网安备 33010602011771号