Fork me on GitHub

版权声明 本站原创文章 由 萌叔 发表
转载请注明 萌叔 | https://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


请我喝瓶饮料

微信支付码

2 对 “gin 优雅退出”的想法;

  1. go server.ListenAndServe()
    请问这里的服务为什么放在协程中呢,刚入门go,看到好多地方都这样写,但没搞清楚为什么

    1. server.ListenAndServe()
      本身会监听在socket上,如果不放到另一个协程中,
      后面的代码

         // 设置优雅退出
          gracefulExitWeb(server)
      

      是无法被执行到的

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注