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

 

1. 背景

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

为什么需要优雅的退出?

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

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

 

2. 常见的几种平滑关闭

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

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

2.1 http server 平滑关闭

原来的写法

// 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 gRPC server 平滑关闭

原来的写法

// 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 worker 协程平滑关闭

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

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 接口的自定义服务平滑关闭

实现 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程序退出的资料请关注编程宝库其它相关文章!

 前言上篇文章gRPC,爆赞直接爆了,内容主要包括:简单的 gRPC 服务,流处理模式,验证器,Token 认证和证书认证。在多个平台的阅读量都创了新高,在 oschina 更是获得 ...