【golang】go app 优雅关机 Graceful Shutdown How?
创始人
2024-03-30 07:15:09
0

 

0 背景     

        我们使用 Go (Golang)来构建一个非常简单的 API 服务器,它可以优雅地关闭。优雅的关闭是指所有进程都被正确关闭,当前正在运行的任何 API 请求都不会被中断,并且将首先完成运行,最后拒绝任何新的 API 请求。

       在本文中,我们将学习构建一个简单的 API 服务器,并了解如何截获外部关机信号,以便通知服务器我们正在关闭。

1 直接关停有什么问题?

  • 服务器立即关闭,go程序不会捕捉到错误!
  • 如果请求正在处理,它将突然停止。正在处理的请求不会得到相应。下面例子中可以通过添加时间来验证这一点,睡眠(10秒 在请求处理程序中延长请求持续时间)。

2. 不优雅关机版本

  • (1)ーー声明一个简单的路由并分配一个处理程序
  • (2)ーー启动服务器(程序将在这里停止,而服务器接受请求)
  • (3)ーー处理服务器退出时停止接受请求的错误)
  • (4)ーー处理请求的简单路由处理程序
package mainimport ("fmt""net/http"
)func main() {fmt.Println("Hello, NO Graceful Server!")// (1) api route and handlerhttp.HandleFunc("/hello", HelloAPI_)// (2) start the servererr := http.ListenAndServe(":8080", nil)// (3) handle error on exitif 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))
}

3. 优雅关机版

  • (1)首先我们需要创建一个多路复用器(简称 mux) ,它基本上是一个请求处理程序
  • (2)使用 mux,我们注册路由和处理程序。这与我们之前使用 http 包所做的几乎相同
  • (3)然后创建服务器。对于这个对象,稍后我们将了解关闭方法
  • (4)然后我们通过调用... ListenAndServe
  • (5)来启动服务器。如果服务器无法启动,我们可能应该记录错误
  • (6)我们需要检查错误是否是由优雅的关闭引起的,并且安全地忽略它
  • (7)我们需要以某种方式调用 Shutdown 方法来优雅地关闭服务器并且拒绝正确的请求,等待任何处理请求完成。
  • (8)最后,如果不能正确执行关机,我们也会记录错误
package mainimport ("context""fmt""net/http""os""os/signal""syscall""time"
)func main() {// print that the application has startedfmt.Println("Hello, Graceful Server!")// create a server muxserverMux := http.NewServeMux()// register route and handlerserverMux.HandleFunc("/hello", HelloAPI__)// create serverserver := &http.Server{Addr:    ":8080",Handler: serverMux,}// create channel to capture signalsigChn := make(chan os.Signal, 1)// register channel for signal capturing// 注册监听系统信号量signal.Notify(sigChn, syscall.SIGINT)// create channel to capture server errorstartErrChn := make(chan error, 1)// start the server asynchronouslygo func() {// (4) start the servererr := server.ListenAndServe()// (5) handle error on exitif err != nil {if err == http.ErrServerClosed {// do nothing...} else {// log errorfmt.Println(err)}}// inform that server has stopped accepting requestsstartErrChn <- err}()// wait for either a Ctrl+C signal, or server abnormal start errorselect {// we captured a signal to shut down applicationcase <-sigChn:// print that server is shutting downfmt.Println("server is shutting down")// trigger the server shutdown gracefullyerr := server.Shutdown(context.Background())// log any error on graceful exitif err != nil {fmt.Println(err)}// we have an error from server's listen and serve, which is abnormal shutdowncase <-startErrChn:// since we already logged the error, we may want to log additional detailsfmt.Println("server abnormal shutdown without stop signal!")}// tidy up and print that we have gracefully shutdown the serverfmt.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.MillisecondnextPollInterval := func() time.Duration {// Add 10% jitter.interval := pollIntervalBase + time.Duration(rand.Intn(int(pollIntervalBase/10)))// Double and clamp for next time.pollIntervalBase *= 2if 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())}}
}

 

 4. 小结&问题

  • 1 上面代码如果ctr+c捕获系统信号之后的处理都能解决:「当前正在运行的任何 API 请求都不会被中断」,但是如果代码主动关停确实不行的,为什么呢?如下改动(HelloAPI_最终没有执行完)
  •  2. 核心方案(拦截信号):我们让 Go 系统在信号被拦截时通知我们,使用channel实现;

 

相关内容

热门资讯

落户政策居然考虑放开! 怎么,我能落户北京了? 大家好,我是孙少睡,这是我的第467篇楼市评论。 很多人的第一反应肯定是有没...
股市必读:ST泉为股东因涉嫌违... 截至2025年12月26日收盘,ST泉为(300716)报收于9.96元,下跌0.8%,换手率0.9...
日本公布犯罪白皮书:2024年... 日本法务省19日公布的2025年版犯罪白皮书显示,日本2024年刑事犯罪案件数量明显上升,其中性犯罪...
中央广电总台副台长王晓真,黑龙... 据央视新闻报道,12月28日,中央广播电视总台《2026年春节联欢晚会》分会场发布。黑龙江哈尔滨、浙...
聚焦全国财政工作会议丨明年财政... (央视财经《经济信息联播》)明年是“十五五”规划的开局之年,财政政策将聚焦哪些关键领域精准发力? ...
原创 中... 12月26日,中国对美国实施了一次重磅反制,针对美国政府前不久批准的111亿美元对台军售,中方决定出...
徐杰11分王少杰遭驱逐 张宁缺... [搜狐体育战报]北京时间12月28日消息,2025-26赛季CBA常规赛继续第7轮角逐。王少杰第三节...
《今日说法》主持人李晓东买茶叶... 12月28日,《今日说法》栏目主持人李晓东发布视频称,此前“被骗1000元买茶叶”事件迎来新进展:该...
3-0领先终于能休息了!莫德里... 在意甲第17轮的一场焦点战中,AC米兰迎战维罗纳。比赛进行到第70分钟时,AC米兰在3-0领先的情况...