V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
Muninn
V2EX  ›  Go 编程语言

使用 Go 1.16 的 signal.NotifyContext 让你的服务重启更优雅

  •  
  •   Muninn ·
    hyacinthus · 2021-04-09 17:56:45 +08:00 · 1987 次点击
    这是一个创建于 1084 天前的主题,其中的信息可能已经有所发展或是发生改变。

    在 Go 1.16 的更新中,signal包增加了一个函数 NotifyContext, 这让我们优雅的重启服务( Graceful Restart )可以写的更加优雅。

    一个服务想要优雅的重启主要包含两个方面:

    • 退出的旧服务需要 Graceful Shutdown,不强制杀进程,不泄漏系统资源。
    • 在一个集群内轮流重启服务实例,保证服务不中断。

    第二个问题跟部署方式相关,改天专门写一篇讨论,今天我们主要谈怎么样优雅的退出。

    首先在代码里,用了外部资源,一定要使用defer去调用Close()方法关闭。 然后我们就要拦截系统的中断信号,保证程序收到中断信号之后,主动有序退出,这样所有的 defer 才会被执行。

    在以前,大概是这么写:

    func everLoop(ctx context.Context) {
    LOOP:
        for {
            select {
            case <-ctx.Done():
                // 收到信号退出无限循环
                break LOOP
            default:
                // 用一个 sleep 模拟业务逻辑
                time.Sleep(time.Second * 10)
            }
        }
    }
    
    func main() {
        // 建立一个可以手动取消的 Context
        ctx, cancel := context.WithCancel(context.Background())
    
        // 监控系统信号,这里只监控了 SIGINT ( Ctrl+c ),SIGTERM
        // 在 systemd 和 docker 中,都是先发 SIGTERM,过一段时间没退出再发 SIGKILL
        // 所以这里没捕获 SIGKILL
        sig := make(chan os.Signal, 1)
        signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
        go func() {
            <-sig
            cancel()
        }()
    
        // 开始无限循环,收到信号就会退出
        everLoop(ctx)
        fmt.Println("graceful shuwdown")
    }
    
    

    现在有了新的函数,这一段变得更简单了:

    func main() {
        // 监控系统信号和创建 Context 现在一步搞定
        ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
        // 在收到信号的时候,会自动触发 ctx 的 Done,这个 stop 是不再捕获注册的信号的意思,算是一种释放资源。
        defer stop()
    
        // 开始无限循环,收到信号就会退出
        everLoop(ctx)
        fmt.Println("graceful shuwdown")
    }
    
    

    感谢 Golang ,当年用别的语言需要写一大堆代码的功能,现在几行就可以轻松实现了。 让它成为你服务程序的标配吧。

    最后,我是写最新的项目LetServerRun的时候,发现这种最新的写法的。 LetServerRun 可以让你把微信公众号当作随身的 Terminal 控制你的服务端。 在它的 Agent 的 main 函数中就有上述用法的示例,欢迎参考。

    9 条回复    2021-04-13 08:49:24 +08:00
    labulaka521
        1
    labulaka521  
       2021-04-09 18:02:00 +08:00 via iPhone
    牛逼
    labulaka521
        2
    labulaka521  
       2021-04-09 18:02:07 +08:00 via iPhone
    很棒这个函数
    ahmcsxcc
        3
    ahmcsxcc  
       2021-04-09 18:06:38 +08:00
    不太明白第一种的写法
    为什么不直接用 sig,还要搞个 context ?
    类似下面这样:

    func everLoop(ctx context.Context) {
    LOOP:
    for {
    select {
    case <-sig:
    // 收到信号退出无限循环
    break LOOP
    default:
    // 用一个 sleep 模拟业务逻辑
    time.Sleep(time.Second * 10)
    }
    }
    }
    ahmcsxcc
        4
    ahmcsxcc  
       2021-04-09 18:07:14 +08:00
    @ahmcsxcc #3 忘了改函数的入参
    Muninn
        5
    Muninn  
    OP
       2021-04-09 18:17:00 +08:00
    @ahmcsxcc 我是模拟常见情况,现在 ctx 是标准,很多第三方的第一个参数都是 ctx 。

    自己单纯的写就用那个信号就好了。

    事实上是需要靠 context 控制一大堆东西退出。
    Aoang
        6
    Aoang  
       2021-04-10 10:55:39 +08:00
    ```go

    func handleSignal() {
    c := make(chan os.Signal)
    signal.Notify(c, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM)

    go func() {
    sig := <-c
    log.Info("server exit signal", zap.Any("signal notify", sig))

    cron.Stop()
    server.Close()

    log.Info("server exit", zap.Time("time", time.Now()))
    os.Exit(0)
    }()
    }

    ```
    Muninn
        7
    Muninn  
    OP
       2021-04-10 13:37:14 +08:00
    @Aoang 对,这个新函数只是改善了本来要用 context 的时候的写法,如果本来不用 context,那就继续用传统的写法就好。

    官方的 http 包,为了兼容历史,是有单独的 shutdown 函数,
    于是各家 web 框架也都用了 http 官方的 shutdown 方法,比如 echo:
    https://echo.labstack.com/cookbook/graceful-shutdown/

    不过一些其他的后台服务,或者自己写的,都已经用 context 控制了。
    eudore
        8
    eudore  
       2021-04-13 08:25:52 +08:00
    没有监听对象的 fd 传递??? 重启不止信号监听
    Muninn
        9
    Muninn  
    OP
       2021-04-13 08:49:24 +08:00
    @eudore 都写 golang 了,部署的时候肯定是 docker / Kubernetes,即使写系统 daemon,也是用 systemd 维护着。

    这几个生态都是用 sigterm 信号去重启的。 没有太大必要自己实现重启。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   1091 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 31ms · UTC 18:53 · PVG 02:53 · LAX 11:53 · JFK 14:53
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.