参考链接
什么是优雅关机?
我们编写的Web项目部署之后,经常会因为需要进行配置变更或功能迭代而重启服务,单纯的
kill -9 pid
的方式会强制关闭进程,这样就会导致服务端当前正在处理的请求失败
优雅关机就是服务端关机命令发出后不是立即关机,而是等待当前还在处理的请求全部处理完毕后再退出程序,是一种对客户端友好的关机方式。而执行Ctrl+C
关闭服务端时,会强制结束进程导致正在访问的请求出现问题。
涉及到的API
Notify
https://pkg.go.dev/os/signal#Notify
第一个参数是一个管道 Chanel !
剩下的可变参数是可以传入一些要监听的信号!
如果可变参数传入了一些要监听的信号,那么只有捕获这些监听信号才会被转发到Chanel
中!
如果没有提供信号,所有传入的信号都会被转发到 Chanel
func Notify(c chan<- os.Signal, sig ...os.Signal)
kill 默认会发送 syscall.SIGTERM 信号
kill -2 发送 syscall.SIGINT 信号,我们常用的Ctrl+C就是触发系统SIGINT信号
kill -9 发送 syscall.SIGKILL 信号,但是不能被捕获,所以不需要添加它
Http.Server
gin
内部启动的http服务,其实是实例化了一个标准库的httpServer!
// gin.go
func (engine *Engine) Run(addr ...string) (err error) {
defer func() { debugPrintError(err) }()
if engine.isUnsafeTrustedProxies() {
debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" +
"Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.")
}
address := resolveAddress(addr)
debugPrint("Listening and serving HTTP on %s\n", address)
err = http.ListenAndServe(address, engine.Handler())
return
}
// server.go
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
func (srv *Server) ListenAndServe() error
为了能从代码层面控制启动和关闭,我们就实例化一个我们自己的 Server 实例了!
// router.Run(":3000")
// 这里我们需要一个Server实例
srv := &http.Server{
Addr: "localhost:3000",
Handler: router,
}
功能实现
package main
import (
"context"
"fmt"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
// 设置一个 5s 后才能响应的请求!
router.GET("/", func(ctx *gin.Context) {
time.Sleep(time.Second * 5)
ctx.JSON(200, gin.H{
"MESSAGE": "OK",
})
})
// router.Run(":3000")
// 这里我们需要一个Server实例
srv := &http.Server{
Addr: "localhost:3000",
Handler: router,
}
// 使用一个 goroutine 跑 web服务,因为ListenAndServe会阻塞main,下面的监听代码执行不到!
go func() {
err := srv.ListenAndServe()
if err != nil {
fmt.Println(err.Error())
}
}()
quit := make(chan os.Signal, 1) // 创建一个接收信号的通道
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) // 此处不会阻塞
fmt.Println("监听信号")
<-quit //如果没有信号发送则会阻塞在这里!
// 如果能执行到这里,代表我们想要的信号来了!
ctx := context.Background()
// 5秒内优雅关闭服务(将未处理完的请求处理完再关闭服务),超过5秒就超时退出
timeoutCtx, cancel := context.WithTimeout(ctx, time.Second*5)
defer cancel()
// Shutdown 接受一个 context 可以控制 Shutdown
err := srv.Shutdown(timeoutCtx)
if err != nil {
fmt.Println(err.Error())
}
}
评论区