
我们使用 Go (Golang)来构建一个非常简单的 API 服务器,它可以优雅地关闭。优雅的关闭是指所有进程都被正确关闭,当前正在运行的任何 API 请求都不会被中断,并且将首先完成运行,最后拒绝任何新的 API 请求。
在本文中,我们将学习构建一个简单的 API 服务器,并了解如何截获外部关机信号,以便通知服务器我们正在关闭。
package mainimport ("fmt""net/http"
)func main() {fmt.Println("Hello, NO Graceful Server!")// (1) api route and handlerhttp.HandleFunc("/hello", HelloAPI_)// (2) start the servererr := http.ListenAndServe(":8080", nil)// (3) handle error on exitif err != nil {panic(err)}
}// (4) A simple basic implementation of handling an API request
func HelloAPI_(w http.ResponseWriter, r *http.Request) {time.Sleep(10 * time.Second)response := "Hello!"w.Write([]byte(response))
}
package mainimport ("context""fmt""net/http""os""os/signal""syscall""time"
)func main() {// print that the application has startedfmt.Println("Hello, Graceful Server!")// create a server muxserverMux := http.NewServeMux()// register route and handlerserverMux.HandleFunc("/hello", HelloAPI__)// create serverserver := &http.Server{Addr: ":8080",Handler: serverMux,}// create channel to capture signalsigChn := make(chan os.Signal, 1)// register channel for signal capturing// 注册监听系统信号量signal.Notify(sigChn, syscall.SIGINT)// create channel to capture server errorstartErrChn := make(chan error, 1)// start the server asynchronouslygo func() {// (4) start the servererr := server.ListenAndServe()// (5) handle error on exitif err != nil {if err == http.ErrServerClosed {// do nothing...} else {// log errorfmt.Println(err)}}// inform that server has stopped accepting requestsstartErrChn <- err}()// wait for either a Ctrl+C signal, or server abnormal start errorselect {// we captured a signal to shut down applicationcase <-sigChn:// print that server is shutting downfmt.Println("server is shutting down")// trigger the server shutdown gracefullyerr := server.Shutdown(context.Background())// log any error on graceful exitif err != nil {fmt.Println(err)}// we have an error from server's listen and serve, which is abnormal shutdowncase <-startErrChn:// since we already logged the error, we may want to log additional detailsfmt.Println("server abnormal shutdown without stop signal!")}// tidy up and print that we have gracefully shutdown the serverfmt.Println("Graceful shutdown!")
}// a long running request
func HelloAPI__(w http.ResponseWriter, r *http.Request) {time.Sleep(150 * time.Second)response := "Hello!"w.Write([]byte(response))
}
// 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 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 (srv *Server) Shutdown(ctx context.Context) error {srv.inShutdown.setTrue()srv.mu.Lock()lnerr := srv.closeListenersLocked()srv.closeDoneChanLocked()for _, f := range srv.onShutdown {go f()}srv.mu.Unlock()pollIntervalBase := time.MillisecondnextPollInterval := func() time.Duration {// Add 10% jitter.interval := pollIntervalBase + time.Duration(rand.Intn(int(pollIntervalBase/10)))// Double and clamp for next time.pollIntervalBase *= 2if pollIntervalBase > shutdownPollIntervalMax {pollIntervalBase = shutdownPollIntervalMax}return interval}timer := time.NewTimer(nextPollInterval())defer timer.Stop()for {if srv.closeIdleConns() && srv.numListeners() == 0 {return lnerr}select {case <-ctx.Done():return ctx.Err()case <-timer.C:timer.Reset(nextPollInterval())}}
}


