版权声明 本站原创文章 由 萌叔 发表
转载请注明 萌叔 | http://vearne.cc

前言

gin底层使用的是net/http, 所以gin的优雅退出就等于 http.Server的优雅退出, Golang 1.8以后提供了Shutdown函数,可以优雅关闭http.Server

func (srv *Server) Shutdown(ctx context.Context) error

优雅退出的过程

  • 1)关闭所有的监听
  • 2)后关闭所有的空闲连接
  • 3)无限期等待活动的连接处理完毕转为空闲,并关闭 如果提供了带有超时的Context,将在服务关闭前返回 Context的超时错误

完整的例子

package main

import (
	"net/http"
	"time"
	"os"
	"os/signal"
	"syscall"
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/pkg/errors"
)

func SlowHandler(c *gin.Context) {
	fmt.Println("[start] SlowHandler")
	//time.Sleep(30 * time.Second)
	time.Sleep(30 * time.Second)
	fmt.Println("[end] SlowHandler")
	c.JSON(http.StatusOK, gin.H{

		"message": "success",
	})
}

// 实现context.Context接口
type ExitContext struct{
	Chan chan struct{}
	DeadLineTime time.Time
}

func NewExitContext(duration time.Duration) *ExitContext{
	cxt :=  ExitContext{}
	cxt.DeadLineTime = time.Now().Add(duration)
	cxt.Chan = make(chan struct{}, 1)
	return &cxt
}

func (cxt *ExitContext) Done() <-chan struct{}{
	if time.Now().After(cxt.DeadLineTime){
		cxt.Chan <- struct{}{}
	}
	return cxt.Chan
}


func (cxt *ExitContext) Err() error{
	return errors.New("can't exit before Specified time")
}

// 无意义的空函数
func(cxt *ExitContext) Value(key interface{}) interface{}{
	return nil
}

func(ctx *ExitContext) Deadline() (deadline time.Time, ok bool){
	deadline = ctx.DeadLineTime
	ok = true
	return
}

func main() {
	r := gin.Default()
	// 1.
	r.GET("/slow", SlowHandler)


	server := &http.Server{
		Addr:           ":8080",
		Handler:        r,
		ReadTimeout:    10 * time.Second,
		WriteTimeout:   10 * time.Second,
		MaxHeaderBytes: 1 << 20,
	}

	go server.ListenAndServe()
	// 设置优雅退出
	gracefulExitWeb(server)
}

func gracefulExitWeb(server *http.Server) {
	ch := make(chan os.Signal)
	signal.Notify(ch, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGINT)
	sig := <-ch

	fmt.Println("got a signal", sig)
	now := time.Now()
	cxt := NewExitContext(3*time.Second)
	err := server.Shutdown(cxt)
	if err != nil{
		fmt.Println("err", err)
	}

	// 看看实际退出所耗费的时间
	fmt.Println("------exited--------", time.Since(now))
}

更简单的写法

package main

import (
	"net/http"
	"time"
	"os"
	"os/signal"
	"syscall"
	"fmt"
	"github.com/gin-gonic/gin"
	"context"
)

func SlowHandler(c *gin.Context) {
	fmt.Println("[start] SlowHandler")
	//time.Sleep(30 * time.Second)
	time.Sleep(30 * time.Second)
	fmt.Println("[end] SlowHandler")
	c.JSON(http.StatusOK, gin.H{

		"message": "success",
	})
}


func main() {
	r := gin.Default()
	// 1.
	r.GET("/slow", SlowHandler)


	server := &http.Server{
		Addr:           ":8080",
		Handler:        r,
		ReadTimeout:    10 * time.Second,
		WriteTimeout:   10 * time.Second,
		MaxHeaderBytes: 1 << 20,
	}

	go server.ListenAndServe()
	// 设置优雅退出
	gracefulExitWeb(server)
}

func gracefulExitWeb(server *http.Server) {
	ch := make(chan os.Signal)
	signal.Notify(ch, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGINT)
	sig := <-ch

	fmt.Println("got a signal", sig)
	now := time.Now()
	cxt, cancel := context.WithTimeout(context.Background(), 5 * time.Second)
	defer cancel()
	err := server.Shutdown(cxt)
	if err != nil{
		fmt.Println("err", err)
	}

	// 看看实际退出所耗费的时间
	fmt.Println("------exited--------", time.Since(now))
}

参考资料1中的例子有错误 如果context 被设置为nil,程序实际是异常崩溃了

log.Println(s.Shutdown(nil))

参考资料

1.Go 1.8 http graceful 体验 2.Gin实践 连载七 Golang优雅重启HTTP服务

后记

萌叔写了一个方便管理多个worker,轻松启动停止的库,欢迎使用 vearne/worker_manager


请我喝瓶饮料

微信支付码