Golang实现程序优雅退出的方法详解

2022-06-15 16:49:49
目录
1. 背景2. 常见的几种平滑关闭2.1 http server 平滑关闭2.2 gRPC server 平滑关闭2.3 worker 协程平滑关闭2.4 实现 io.Closer 接口的自定义服务平滑关闭2.5 集成其他框架怎么做

1.>

项目开发过程中,随着需求的迭代,代码的发布会频繁进行,在发布过程中,如何让程序做到优雅的退出?

为什么需要优雅的退出?

    你的 http 服务,监听端口没有关闭,客户的请求发过来了,但处理了一半,可能造成脏数据。你的协程 worker 的一个任务运行了一半,程序退出了,结果不符合预期。

    如下我们以 http 服务,gRPC 服务,单独的 woker 协程为例子,一步步说明平滑关闭的写法。

    2.>

    为了解决退出可能出现的潜在问题,平滑关闭一般做如下一些事情

      关闭对外的监听端口,拒绝新的连接关闭异步运行的协程关闭依赖的资源等待如上资源关闭然后平滑关闭

      2.1>

      原来的写法

      // startHttpServer start http server
      func startHttpServer() {
          mux := http.NewServeMux()
          // mux.Handle("/metrics", promhttp.Handler())
          if err := http.ListenAndServe(":1608", mux); err != nil {
              log.Fatal("startHttpServer ListenAndServe error: " + err.Error())
          }
      } 

      带平滑关闭的写法

      // startHttpServer start http server
      func startHttpServer() {
          mux := http.NewServeMux()
          // mux.Handle("/metrics", promhttp.Handler())
          srv := &http.Server{
              Addr:    ":1608",
              Handler: mux,
          }
          // 注册平滑关闭,退出时会调用 srv.Shutdown(ctx)
          quit.GetQuitEvent().RegisterQuitCloser(srv)
          if err := srv.ListenAndServe(); err != nil {
              log.Fatal("startHttpServer ListenAndServe error: " + err.Error())
          }
      }
      

      把平滑关闭注册到http.Server的关闭函数中

      // startHttpServer start http server
      func startHttpServer() {
          mux := http.NewServeMux()
          // mux.Handle("/metrics", promhttp.Handler())
          srv := &http.Server{
              Addr:    ":1608",
              Handler: mux,
          }
          // 把平滑退出注册到http.Server中
          srv.RegisterOnShutdown(quit.GetQuitEvent().GracefulStop)
          if err := srv.ListenAndServe(); err != nil {
              log.Fatal("startHttpServer ListenAndServe error: " + err.Error())
          }
      }
      

      2.2>

      原来的写法

      // startGrpcServer start grpc server
      func startGrpcServer() {
          listen, err := net.Listen("tcp", "0.0.0.0:9999")
          if err != nil {
              log.Fatalf("Failed to listen: %v", err)
              return
          }
          grpcServer := grpc.NewServer()
          // helloBoot.GrpcRegister(grpcServer)
          go grpcServer.Serve(listen)
          defer grpcServer.GracefulStop()
          // ...
      }
      

      带平滑关闭的写法 

      // startGrpcServer start grpc server
      func startGrpcServer() {
          listen, err := net.Listen("tcp", "0.0.0.0:9999")
          if err != nil {
              log.Fatalf("Failed to listen: %v", err)
              return
          }
          grpcServer := grpc.NewServer()
          // helloBoot.GrpcRegister(grpcServer)
          go grpcServer.Serve(listen)
          // 把 grpc 的GracefulStop注册到退出事件中
          quit.GetQuitEvent().RegisterStopFunc(grpcServer.GracefulStop)
          quit.WaitSignal()
      }  

      2.3>

      单独的协程启停,可以通过计数的方式注册到退出事件处理器中。

      1.启动协程 增加计数

      quit.GetQuitEvent().AddGoroutine()

      2.停止协程 减计数 

      quit.GetQuitEvent().DoneGoroutine()

      3.常驻后台运行的协程退出的条件改成退出事件是否结束的条件 

      !quit.GetQuitEvent().HasFired()

      4.常驻后台运行的协程若通过 select 处理 chan,同时增加退出事件的chan

      case <-quit.GetQuitEvent().Done()

      // myWorker my worker
      type myWorker struct {
      }
       
      // RunWorkerWithChan run Goroutine worker
      func (m *myWorker) RunWorkerWithChan() {
          // 启动一个Goroutine时,增加Goroutine数
          quit.GetQuitEvent().AddGoroutine()
          defer func() {
              // 一个Goroutine退出时,减少Goroutine数
              quit.GetQuitEvent().DoneGoroutine()
          }()
          // 退出时,此次退出
          for !quit.GetQuitEvent().HasFired() {
              select {
              // 退出时,收到退出信号
              case <-quit.GetQuitEvent().Done():
                  break
                  //case msg := <- m.YouChan:
                  // handle msg
              }
          }
      }
       
      // RunWorker run Goroutine worker
      func (m *myWorker) RunWorker() {
          // 启动一个Goroutine时,增加Goroutine数
          quit.GetQuitEvent().AddGoroutine()
          defer func() {
              // 一个Goroutine退出时,减少Goroutine数
              quit.GetQuitEvent().DoneGoroutine()
          }()
       
          // 退出时,此次退出
          for !quit.GetQuitEvent().HasFired() {
              // ...
          }
      }
      

      2.4>

      实现 io.Closer 接口的结构体,增加到退出事件处理器中 

      // startMyService start my service
      func startMyService() {
          srv := NewMyService()
          // 注册平滑关闭,退出时会调用 srv.Close()
          quit.GetQuitEvent().RegisterCloser(srv)
          srv.Run()
      }
       
      // myService my service
      type myService struct {
          isStop bool
      }
       
      // NewMyService new
      func NewMyService() *myService {
          return &myService{}
      }
       
      // Close my service
      func (m *myService) Close() error {
          m.isStop = true
          return nil
      }
       
      // Run my service
      func (m *myService) Run() {
          for !m.isStop {
              // ....
          }
      }
      

      2.5>

      退出信号处理由某一框架接管,寻找框架如何注册退出函数,优秀的框架一般都会实现安全实现退出的机制。

      如下将退出事件注册到某一框架的平滑关闭函数中

      func startMyServer() {
          // ...
          // xxx框架退出函数注册退出事件
          xxx.RegisterQuitter(func() {
              quit.GetQuitEvent().GracefulStop()
          })
      }

      以上就是Golang实现程序优雅退出的方法详解的详细内容,更多关于Golang程序退出的资料请关注易采站长站其它相关文章!