• 【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 协程平滑关闭

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

    • 启动协程 增加计数
      •  quit.GetQuitEvent().AddGoroutine()
    • 停止协程 减计数 
      •  quit.GetQuitEvent().DoneGoroutine()
    • 常驻后台运行的协程退出的条件改成退出事件是否结束的条件 
      • !quit.GetQuitEvent().HasFired()
    • 常驻后台运行的协程若通过 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()
    	})
    }
    

    参考:

    https://github.com/mygityf/go-library/blob/main/quit/quit.go

    完。

    祝玩的开心~

  • 相关阅读:
    javascript 之迭代器
    前端数据结构--二叉树先序、中序、后序 递归、非递归遍历
    前端数据结构--树
    前端数据结构--散列表(哈希表)
    前端数据结构--线性结构-队列、栈
    前端数据结构--线性结构-链表
    前端数据结构--线性结构-数组
    前端数据结构---复杂度分析
    前端数据结构---相关基础概念
    css整理之-----------基本知识
  • 原文地址:https://www.cnblogs.com/voipman/p/16372385.html
Copyright © 2020-2023  润新知