最新资讯:Golang实现程序优雅退出的方法详解
目录
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() }) }
以上就是Golang实现程序优雅退出的方法详解的详细内容,更多关于Golang程序退出的资料请关注脚本之家其它相关文章!
X 关闭
X 关闭