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


如果我的文章对你有帮助,你可以给我打赏以促使我拿出更多的时间和精力来分享我的经验和思考总结。

微信支付码

发表评论

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.