乐趣区

关于go:GoGraceful-Shutdown

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 main

import (
    "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 main

import (
    "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 start
2023/05/12 16:48:54 Shutdown finish
2023/05/12 16:48:57 Called on Shutdown
2023/05/12 16:48:57 Exit func done

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

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

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

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

退出移动版