目录
- 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 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() }) }