• 【golang】go app 优雅关机 Graceful Shutdown How?


     

    0 背景     

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

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

    1 直接关停有什么问题?

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

    2. 不优雅关机版本

    • (1)ーー声明一个简单的路由并分配一个处理程序
    • (2)ーー启动服务器(程序将在这里停止,而服务器接受请求)
    • (3)ーー处理服务器退出时停止接受请求的错误)
    • (4)ーー处理请求的简单路由处理程序
    1. package main
    2. import (
    3. "fmt"
    4. "net/http"
    5. )
    6. func main() {
    7. fmt.Println("Hello, NO Graceful Server!")
    8. // (1) api route and handler
    9. http.HandleFunc("/hello", HelloAPI_)
    10. // (2) start the server
    11. err := http.ListenAndServe(":8080", nil)
    12. // (3) handle error on exit
    13. if err != nil {
    14. panic(err)
    15. }
    16. }
    17. // (4) A simple basic implementation of handling an API request
    18. func HelloAPI_(w http.ResponseWriter, r *http.Request) {
    19. time.Sleep(10 * time.Second)
    20. response := "Hello!"
    21. w.Write([]byte(response))
    22. }

    3. 优雅关机版

    • (1)首先我们需要创建一个多路复用器(简称 mux) ,它基本上是一个请求处理程序
    • (2)使用 mux,我们注册路由和处理程序。这与我们之前使用 http 包所做的几乎相同
    • (3)然后创建服务器。对于这个对象,稍后我们将了解关闭方法
    • (4)然后我们通过调用... ListenAndServe
    • (5)来启动服务器。如果服务器无法启动,我们可能应该记录错误
    • (6)我们需要检查错误是否是由优雅的关闭引起的,并且安全地忽略它
    • (7)我们需要以某种方式调用 Shutdown 方法来优雅地关闭服务器并且拒绝正确的请求,等待任何处理请求完成。
    • (8)最后,如果不能正确执行关机,我们也会记录错误
    1. package main
    2. import (
    3. "context"
    4. "fmt"
    5. "net/http"
    6. "os"
    7. "os/signal"
    8. "syscall"
    9. "time"
    10. )
    11. func main() {
    12. // print that the application has started
    13. fmt.Println("Hello, Graceful Server!")
    14. // create a server mux
    15. serverMux := http.NewServeMux()
    16. // register route and handler
    17. serverMux.HandleFunc("/hello", HelloAPI__)
    18. // create server
    19. server := &http.Server{
    20. Addr: ":8080",
    21. Handler: serverMux,
    22. }
    23. // create channel to capture signal
    24. sigChn := make(chan os.Signal, 1)
    25. // register channel for signal capturing
    26. // 注册监听系统信号量
    27. signal.Notify(sigChn, syscall.SIGINT)
    28. // create channel to capture server error
    29. startErrChn := make(chan error, 1)
    30. // start the server asynchronously
    31. go func() {
    32. // (4) start the server
    33. err := server.ListenAndServe()
    34. // (5) handle error on exit
    35. if err != nil {
    36. if err == http.ErrServerClosed {
    37. // do nothing...
    38. } else {
    39. // log error
    40. fmt.Println(err)
    41. }
    42. }
    43. // inform that server has stopped accepting requests
    44. startErrChn <- err
    45. }()
    46. // wait for either a Ctrl+C signal, or server abnormal start error
    47. select {
    48. // we captured a signal to shut down application
    49. case <-sigChn:
    50. // print that server is shutting down
    51. fmt.Println("server is shutting down")
    52. // trigger the server shutdown gracefully
    53. err := server.Shutdown(context.Background())
    54. // log any error on graceful exit
    55. if err != nil {
    56. fmt.Println(err)
    57. }
    58. // we have an error from server's listen and serve, which is abnormal shutdown
    59. case <-startErrChn:
    60. // since we already logged the error, we may want to log additional details
    61. fmt.Println("server abnormal shutdown without stop signal!")
    62. }
    63. // tidy up and print that we have gracefully shutdown the server
    64. fmt.Println("Graceful shutdown!")
    65. }
    66. // a long running request
    67. func HelloAPI__(w http.ResponseWriter, r *http.Request) {
    68. time.Sleep(150 * time.Second)
    69. response := "Hello!"
    70. w.Write([]byte(response))
    71. }
    1. // Shutdown gracefully shuts down the server without interrupting any
    2. // active connections. Shutdown works by first closing all open
    3. // listeners, then closing all idle connections, and then waiting
    4. // indefinitely for connections to return to idle and then shut down.
    5. // If the provided context expires before the shutdown is complete,
    6. // Shutdown returns the context's error, otherwise it returns any
    7. // error returned from closing the Server's underlying Listener(s).
    8. //
    9. // When Shutdown is called, Serve, ListenAndServe, and
    10. // ListenAndServeTLS immediately return ErrServerClosed. Make sure the
    11. // program doesn't exit and waits instead for Shutdown to return.
    12. //
    13. // Shutdown does not attempt to close nor wait for hijacked
    14. // connections such as WebSockets. The caller of Shutdown should
    15. // separately notify such long-lived connections of shutdown and wait
    16. // for them to close, if desired. See RegisterOnShutdown for a way to
    17. // register shutdown notification functions.
    18. //
    19. // Once Shutdown has been called on a server, it may not be reused;
    20. // future calls to methods such as Serve will return ErrServerClosed.
    21. func (srv *Server) Shutdown(ctx context.Context) error {
    22. srv.inShutdown.setTrue()
    23. srv.mu.Lock()
    24. lnerr := srv.closeListenersLocked()
    25. srv.closeDoneChanLocked()
    26. for _, f := range srv.onShutdown {
    27. go f()
    28. }
    29. srv.mu.Unlock()
    30. pollIntervalBase := time.Millisecond
    31. nextPollInterval := func() time.Duration {
    32. // Add 10% jitter.
    33. interval := pollIntervalBase + time.Duration(rand.Intn(int(pollIntervalBase/10)))
    34. // Double and clamp for next time.
    35. pollIntervalBase *= 2
    36. if pollIntervalBase > shutdownPollIntervalMax {
    37. pollIntervalBase = shutdownPollIntervalMax
    38. }
    39. return interval
    40. }
    41. timer := time.NewTimer(nextPollInterval())
    42. defer timer.Stop()
    43. for {
    44. if srv.closeIdleConns() && srv.numListeners() == 0 {
    45. return lnerr
    46. }
    47. select {
    48. case <-ctx.Done():
    49. return ctx.Err()
    50. case <-timer.C:
    51. timer.Reset(nextPollInterval())
    52. }
    53. }
    54. }

     

     4. 小结&问题

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

     

  • 相关阅读:
    线程池里对异常的处理方式
    mybatis插入数据不返回主键id可能原因及解决
    RPA-1、开启之旅
    MQ - 34 基础功能:在消息队列内核中支持WebSocket的设计
    java计算机毕业设计积分权益商城源码+mysql数据库+系统+lw文档+部署
    redis笔记
    1.14 - 流水线
    【英语:基础高阶_全场景覆盖表达】K11.口语主题陈述——事物类
    一个矩形微带贴片天线的调试
    多线程(2)
  • 原文地址:https://blog.csdn.net/qfzhangwei/article/details/127466511