使用 Go 1.16 的 signal.NotifyContext 让你的服务重启更优雅 - V2EX
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 2645 次点击
    这是一个创建于 1645 天前的主题,其中的信息可能已经有所发展或是发生改变。

    在 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     Solana     879 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 33ms UTC 20:43 PVG 04:43 LAX 13:43 JFK 16:43
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86