Go1.8之后有了Shutdown办法,用来优雅的敞开(Graceful Shutdown)服务。

func (srv *Server) Shutdown(ctx context.Context) error

这个办法会在调用时进行下述操作:
1.敞开所有open listeners
2.敞开所有idle connections
3.无限期期待connections回归idle状态
4.之后敞开服务
注:3的无限期期待能够用context的超时来解决。

Go1.9之后有了RegisterOnShutdown办法,用来在优雅的敞开服务的同时解决一些退出工作。

func (srv *Server) RegisterOnShutdown(f func())

对于RegisterOnShutdown如何失效,咱们看代码。

type Server struct {    ...    onShutdown []func()}func (srv *Server) RegisterOnShutdown(f func()) {    srv.mu.Lock()    srv.onShutdown = append(srv.onShutdown, f)    srv.mu.Unlock()}func (srv *Server) Shutdown(ctx context.Context) error {    ...    srv.mu.Lock()    ...    for _, f := range srv.onShutdown {        go f()    }    srv.mu.Unlock()    ...}

由代码可知,在通过RegisterOnShutdown进行注册之后,这些函数被放入一个函数切片中,并在Shutdown被调用的时候,为每个函数启动了一个goroutine进行解决。
但因为没有进行后续解决,于是这里有个小问题,Shutdown是不期待这些处理函数完结的,就可能比这些处理函数先实现并进而完结程序。

package mainimport (    "context"    "log"    "net/http"    "os"    "os/signal"    "time")func main() {    var srv http.Server    srv.RegisterOnShutdown(func() {        time.Sleep(3 * time.Second)        log.Println("Exit func")    })    idleConnsClosed := make(chan struct{})    go func() {        sigint := make(chan os.Signal, 1)        signal.Notify(sigint, os.Interrupt)        <-sigint        // We received an interrupt signal, shut down.        if err := srv.Shutdown(context.Background()); err != nil {            // Error from closing listeners, or context timeout:            log.Printf("HTTP server Shutdown: %v", err)        }        close(idleConnsClosed)    }()    if err := srv.ListenAndServe(); err != http.ErrServerClosed {        // Error starting or closing listener:        log.Fatalf("HTTP server ListenAndServe: %v", err)    }    <-idleConnsClosed}

运行时Ctrl+c之后,服务立即完结了,并未期待log.Println("Exit func")的输入。

为了解决这个问题咱们应用sync.WaitGroup来解决这些退出函数,于是咱们有了上面的代码(解耦并减少了一些日志):

package mainimport (    "context"    "log"    "net/http"    "os"    "os/signal"    "sync"    "time")func main() {    var srv http.Server    var wg sync.WaitGroup    register := func(f func()) {        wg.Add(1)        log.Println("Exit func register")        srv.RegisterOnShutdown(func() {            defer wg.Done()            f()            log.Println("Exit func done")        })    }    register(func() {        time.Sleep(3 * time.Second)        log.Println("Called on Shutdown")    })    idleConnsClosed := make(chan struct{})    go func() {        sigint := make(chan os.Signal, 1)        signal.Notify(sigint, os.Interrupt)        <-sigint        // We received an interrupt signal, shut down.        log.Println("Shutdown start")        if err := srv.Shutdown(context.Background()); err != nil {            // Error from closing listeners, or context timeout:            log.Printf("HTTP server Shutdown: %v", err)        }        close(idleConnsClosed)    }()    if err := srv.ListenAndServe(); err != http.ErrServerClosed {        // Error starting or closing listener:        log.Fatalf("HTTP server ListenAndServe: %v", err)    }    <-idleConnsClosed    log.Println("Shutdown finish")    wg.Wait()}

咱们在RegisterOnShutdown时应用了time.Sleep(3 * time.Second)来模仿一个长时间的退出程序,并且在"Shutdown finish"之后进行期待,期待退出程序实现工作。
执行日志如下:

2023/05/12 16:48:53 Exit func register^C2023/05/12 16:48:54 Shutdown start2023/05/12 16:48:54 Shutdown finish2023/05/12 16:48:57 Called on Shutdown2023/05/12 16:48:57 Exit func done

能够看到这个服务自身很轻,Shutdown start之后立即就finish并来到wg.Wait(),并期待register的函数实现之后再退出程序。

另外,如果在服务运行的过程中须要启动一些其余的工作,也能够应用

wg.Add(1)go func() {    defer wg.Done()    ...}

来保障工作在服务Shutdown之后能够顺利完成。