go语言gin框架平滑关闭——思悟项目技术2
目录
前言
直接关闭的缺陷
平滑关闭的使用场景
例子
思悟项目:
golang qq邮件发送验证码——思悟项目技术1
前言
平滑关闭(graceful shutdown)是指在停止服务时,能够让现有的连接、任务或者操作优雅地完成,而不是直接中断。
平滑关闭的核心思想是在系统接收到停止信号后,不再接收新请求,只处理当前正在执行的请求,确保所有请求完成后,系统才正式关闭。
直接关闭的缺陷
比如说有一个web服务,我们要升级web服务,也就是版本迭代。但是在升级前,要先把服务关闭。我们可以直接包里终止程序,然后启动新的服务,但是这样做存在缺陷:
- 当前的请求可能会被中断,导致数据丢失。
- 未完成的后台任务会被强行中断。(用户体验感也会很差)
- 数据库连接、文件等资源可能没有机会释放,导致潜在的资源泄漏。
平滑关闭的使用场景
- 项目版本迭代
- 服务重启维护
- 服务迁移
- 防止数据丢失
(例如王者荣耀更新时,正在打游戏的玩家不会更新,等到这局游戏结束后才会进行更新。)
例子
package main
import (
"context"
"errors"
"github.com/gin-gonic/gin"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)
var i = 0
func main() {
router := gin.Default()
// 创建两个接口,一个延迟9秒钟返回信息
router.GET("/a", func(c *gin.Context) {
time.Sleep(9 * time.Second)
i++
c.JSON(http.StatusOK, gin.H{
"num": i,
})
})
// 一个立刻返回信息
router.GET("/b", func(c *gin.Context) {
i++
c.JSON(http.StatusOK, gin.H{
"num": i,
})
})
// 创建一个 http.Server
srv := &http.Server{
Addr: ":8080",
Handler: router,
}
// 在协程中启动服务器
go func() {
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
log.Fatalf("listen: %s\n", err)
}
}()
// 创建信号通道,监听 SIGINT 和 SIGTERM
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
// 阻塞直到收到信号
si := <-quit
log.Println("Shutting down server...", si)
// shutdown方法需要传入一个上下文参数,有两种写法:
// 1.带超时,接收到信号之后,9秒之后无论当前请求是否完成都强制断开
ctx, cancel := context.WithTimeout(context.Background(), 9*time.Second)
// 2.不带超时,等待当前请求全部完成再断开
// ctx, cancel = context.WithCancel(context.Background())
defer cancel()
// 调用 Shutdown 方法平滑关闭
if err := srv.Shutdown(ctx); err != nil {
// 当请求还在的时候强制断开了连接将产生错误,err不为空
log.Fatal("Server forced to shutdown:", err)
}
log.Println("Server exiting")
}
步骤具体为:
- 捕获信号:使用 os/signal 包捕获终止信号。
- 创建 context:创建一个 context,当捕获到信号时触发 context 的取消,从而让正在进行的任务停止接收新的请求。
- 调用 Shutdown 方法:http.Server 提供了一个 Shutdown 方法,接受一个 context,它会让服务器停止接收新的请求,等待处理完当前正在进行的请求,等待时间由 context 决定。
参考:Golang 平滑重启之优雅关机