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
  • 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
}

这里能看到标准库的注释

  • 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)
}

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")
}
  • 启动服务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) //打开监听端口
	}()
}
  • 在main函数中创建error管道,缓冲区大小设置为2(ERROR+SIGTERM)
  • 监听系统退出+监听API路由组创建是否异常
  • 错误均写入errs, 在main函数最后,阻塞等待errs管道后退出程序
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值