• golang优雅退出


    优雅退出

    graceful shutdown,优雅退出。

    指HTTP服务接受到用户的退出指令后停止接收新请求,在处理和回复当前正在处理的这批请求后主动退出服务。

    区别于SIGKILL(kill -9 or CTRL + C),安全退出可以最小化程序在滚动更新时的服务抖动

    用户的退出指令一般是SIGTERM(k8s的实现)或SIGINT(常常对应bash的Ctrl + C

    一、 涉及模块

    1、 监听信号

    使用标准库os/exec.go中Signal即可完成信息监听

      // 至少设置数量为1的缓存区
      quitSignal := make(chan os.Signal, 1)
      signal.Notify(quitSignal, []os.Signal{syscall.SIGINT, syscall.SIGTERM}...)
    
      // 阻塞直至有信号写入
    	<-quitSignal
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • SIGINT:当你在终端按下ctrl+c时,则会触发这个信号
    • SIGTERM:当我们给程序发送kill或者killall指令时,则会触发这个信号

    值得注意的是,在没有使用signal.Notify()时,Go默认有一套信号处理规则,比如 SIGHUP, SIGINTSIGTERM会让程序直接退出。

    2、 停止HTTP服务

    调用运行中的Server实例的Shutdown()方法可以让服务安全退出:

    // ListenAndServe listens on the TCP network address srv.Addr and then
    // calls Serve to handle requests on incoming connections.
    // Accepted connections are configured to enable TCP keep-alives.
    //
    // If srv.Addr is blank, ":http" is used.
    //
    // ListenAndServe always returns a non-nil error. After Shutdown or Close,
    // the returned error is ErrServerClosed.
    func (srv *Server) ListenAndServe() error {
    	if srv.shuttingDown() {
    		return ErrServerClosed
    	}
    	addr := srv.Addr
    	if addr == "" {
    		addr = ":http"
    	}
    	ln, err := net.Listen("tcp", addr)
    	if err != nil {
    		return err
    	}
    	return srv.Serve(ln)
    }
    
    // When Shutdown is called, Serve, ListenAndServe, and
    // ListenAndServeTLS immediately return ErrServerClosed. Make sure the
    // program doesn't exit and waits instead for Shutdown to return.
    
    // Once Shutdown has been called on a server, it may not be reused;
    // future calls to methods such as Serve will return ErrServerClosed.
    func (srv *Server) Shutdown(ctx context.Context) error {
      xxx
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    这里能看到标准库的注释

    • ListenAndServe会在ShutdownClose立即返回 ErrServerClosed
    • Shutdown执行完成时,确保程序不会被退出而是等待以返回

    3、 超时处理

    server的Shutdown方法需要接收一个context对象,因此我们可以定义一个设置超时的context,如果超过这个时间请求还没完成处理,则会强制退出,避免程序长时间等待无法退出

    当然也可以传入一个没有超时的context(context.Background())

    二、 代码实现

    handler方法,通过num参数进行短暂休眠,并打印休眠持续时间。

    简单Demo,未加数据校验。

    func handler(w http.ResponseWriter, r *http.Request) {
    	numStr := r.URL.Query().Get("num")
    	num, err := strconv.Atoi(numStr)
    	if err != nil {
    		return
    	}
    	delay := time.Duration(num) * time.Second
    	startAt := time.Now()
    	fmt.Println("req received, delay", delay)
    	defer func() {
    		fmt.Println("req completed, latency", time.Since(startAt))
    	}()
    
    	time.Sleep(delay)
    	w.WriteHeader(http.StatusOK)
    	_, _ = io.WriteString(w, numStr)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    main函数,创建http服务、启动服务、监听系统退出信号、超时处理。

    func main() {
    	// 创建一个新的HTTP服务器
    	mux := http.NewServeMux()
    	mux.HandleFunc("/process", handler)
    	server := &http.Server{
    		Addr:    ":8080",
    		Handler: mux,
    	}
    
    	// 在一个新的goroutine中启动服务器
    	go func() {
    		if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
    			fmt.Printf("listen: %s\n", err)
    		}
    	}()
    
    	// 监听系统退出信号
    	quit := make(chan os.Signal, 1)
    	signal.Notify(quit, []os.Signal{syscall.SIGINT, syscall.SIGTERM}...)
    	fmt.Println(fmt.Sprintf("\n exit: %v", <-quit))
    
    	// 创建一个带有超时的context,以便在服务器关闭时有一个限制时间
    	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    	defer cancel()
    	if err := server.Shutdown(ctx); err != nil {
    		fmt.Printf("Server Shutdown: %s, time out\n", err)
    	}
    	fmt.Println("Server exiting")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 启动服务ListenAndServe()会阻塞程序,为了避免后续的信号监测被阻塞,因此需要把服务启动放到协程执行。
    • 根据设置超时时间和handler传参的大小,体验服务是否超时的返回结果有什么不同

    三、 业务场景

    代码实现只是一个简单的demo,在实际应用场景下,并不会简单地、草率地开启这样一个http服务,一方便程序要监听系统的退出信号,另一方面在程序拉起时创建routerGroup路由组、LoadConfig加载配置等出现错误时也应中断程序并给出对应的错误信息。

    func main() {
    
      	runtime.GOMAXPROCS(runtime.NumCPU())
      	...
      
      	errs := make(chan error, 2)
     	go func() {
    		c := make(chan os.Signal)
    		signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
    		errs <- fmt.Errorf("%s", <-c)
    	}()
    	
     	hostAPI(errs)
    	...
      
    	fmt.Println(fmt.Sprintf("exit: %v", <-errs))
    }
    
    func hostAPI(errs chan error) {
    	server := GetServer() // 表示获取Server对象,伪代码
    	  router := gin.Default()
    	if svr == nil {
    		log.Warnf(" [%s] server config is nil", name)
    		return
    	}
    	log.Infof("host [%s] server [%s,%d]", svr.Name, svr.Host, svr.Port)
    	go func() {
    		strPort := strconv.Itoa(svr.Port)
    		listenAddr := svr.Host + ":" + strPort
    		fmt.Println("hosts:", listenAddr)
    		errs <- http.ListenAndServe(listenAddr, router) //打开监听端口
    	}()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 在main函数中创建error管道,缓冲区大小设置为2(ERROR+SIGTERM)
    • 监听系统退出+监听API路由组创建是否异常
    • 错误均写入errs, 在main函数最后,阻塞等待errs管道后退出程序
  • 相关阅读:
    中国石油大学(北京)-《 渗流力学》第三阶段在线作业
    Vite2+Vue3+ts的eslint设置踩坑
    2023.11.4 Idea 配置国内 Maven 源
    GD32F303固件库开发(14)----IIC之配置OLED
    零基础Linux_10(进程)进程终止(main函数的返回值)+进程等待
    bugku-web-XXX二手车交易市场
    鸿蒙(API 12 Beta3版)【获取支持的编解码能力】 音视频编码
    Fibonacci数列那些事!
    基于Java网络安全宣传网站设计实现(源码+lw+部署文档+讲解等)
    在微服务架构中使用JWT
  • 原文地址:https://blog.csdn.net/qq_45366447/article/details/134458510