
我们使用 Go (Golang)来构建一个非常简单的 API 服务器,它可以优雅地关闭。优雅的关闭是指所有进程都被正确关闭,当前正在运行的任何 API 请求都不会被中断,并且将首先完成运行,最后拒绝任何新的 API 请求。
在本文中,我们将学习构建一个简单的 API 服务器,并了解如何截获外部关机信号,以便通知服务器我们正在关闭。
- package main
-
- import (
- "fmt"
- "net/http"
- )
-
- func main() {
- fmt.Println("Hello, NO Graceful Server!")
-
- // (1) api route and handler
- http.HandleFunc("/hello", HelloAPI_)
-
- // (2) start the server
- err := http.ListenAndServe(":8080", nil)
-
- // (3) handle error on exit
- if 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 main
-
- import (
- "context"
- "fmt"
- "net/http"
- "os"
- "os/signal"
- "syscall"
- "time"
- )
-
- func main() {
- // print that the application has started
- fmt.Println("Hello, Graceful Server!")
-
- // create a server mux
- serverMux := http.NewServeMux()
-
- // register route and handler
- serverMux.HandleFunc("/hello", HelloAPI__)
-
- // create server
- server := &http.Server{
- Addr: ":8080",
- Handler: serverMux,
- }
-
- // create channel to capture signal
- sigChn := make(chan os.Signal, 1)
-
- // register channel for signal capturing
- // 注册监听系统信号量
- signal.Notify(sigChn, syscall.SIGINT)
-
- // create channel to capture server error
- startErrChn := make(chan error, 1)
-
- // start the server asynchronously
- go func() {
- // (4) start the server
- err := server.ListenAndServe()
-
- // (5) handle error on exit
- if err != nil {
- if err == http.ErrServerClosed {
- // do nothing...
- } else {
- // log error
- fmt.Println(err)
- }
- }
-
- // inform that server has stopped accepting requests
- startErrChn <- err
- }()
-
- // wait for either a Ctrl+C signal, or server abnormal start error
- select {
-
- // we captured a signal to shut down application
- case <-sigChn:
- // print that server is shutting down
- fmt.Println("server is shutting down")
-
- // trigger the server shutdown gracefully
- err := server.Shutdown(context.Background())
-
- // log any error on graceful exit
- if err != nil {
- fmt.Println(err)
- }
-
- // we have an error from server's listen and serve, which is abnormal shutdown
- case <-startErrChn:
- // since we already logged the error, we may want to log additional details
- fmt.Println("server abnormal shutdown without stop signal!")
- }
- // tidy up and print that we have gracefully shutdown the server
- fmt.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.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 srv.closeIdleConns() && srv.numListeners() == 0 {
- return lnerr
- }
- select {
- case <-ctx.Done():
- return ctx.Err()
- case <-timer.C:
- timer.Reset(nextPollInterval())
- }
- }
- }


