Go Web开发之Gin多服务配置及优雅关闭平滑重启实现方法
如何自定义Gin服务配置及其启动多个服务?
描述: 在Gin的生产环境中通常会自定义HTTP配置以达到最优性能,此处我们简单一下 Server 结构体中可配置的参数项。
// A Server defines parameters for running an HTTP server. // The zero value for Server is a valid configuration. type Server struct { // 配置监听地址:端口,默认是:8080 Addr string // 要调用的处理程序,http.DefaultServeMux如果为nil Handler Handler // 如果为true,则将“OPTIONS*”请求传递给Handler DisableGeneralOptionsHandler bool // 提供TLS配置 TLSConfig *tls.Config //读取整个请求(包括正文)的最长持续时间。 ReadTimeout time.Duration // 读取整请求(Header)的最长持续时间。 ReadHeaderTimeout time.Duration // 超时写入响应之前的最长持续时间 WriteTimeout time.Duration // 启用保持活动时等待下一个请求的最长时间 IdleTimeout time.Duration // 控制服务器解析请求标头的键和值(包括请求行)时读取的最大字节数 (通常情况下不进行设置) MaxHeaderBytes int // 在发生ALPN协议升级时接管所提供TLS连接的所有权。 TLSNextProto map[string]func(*Server, *tls.Conn, Handler) // 指定了一个可选的回调函数,当客户端连接更改状态时调用该函数 ConnState func(net.Conn, ConnState) // 为接受连接的错误、处理程序的意外行为以及潜在的FileSystem错误指定了一个可选的记录器 ErrorLog *log.Logger // 返回/此服务器上传入请求的基本上下文 BaseContext func(net.Listener) context.Context // 指定一个函数来修改用于新连接c的上下 ConnContext func(ctx context.Context, c net.Conn) context.Context // 当服务器处于关闭状态时为true inShutdown atomic.Bool disableKeepAlives atomic.Bool nextProtoOnce sync.Once // guards setupHTTP2_* init nextProtoErr error // result of http2.ConfigureServer if used mu sync.Mutex listeners map[*net.Listener]struct{} activeConn map[*conn]struct{} onShutdown []func() listenerGroup sync.WaitGroup }
模块更新
go get -u golang.org/x/sync/errgroup go mod tidy
示例代码:
package main import ( "log" "net/http" "time" "github.com/gin-gonic/gin" "golang.org/x/sync/errgroup" ) // 处理属于同一总体任务的子任务的goroutine的集合 var ( g errgroup.Group ) // s2 Gin 服务的 Handler func router02() http.Handler { e := gin.New() e.Use(gin.Recovery()) e.GET("/", func(c *gin.Context) { c.JSON( http.StatusOK, gin.H{ "code": http.StatusOK, "msg": "Welcome server 02 blog.weiyigeek.top", }, ) }) return e } func main() { // Default返回一个Engine实例,该实例已连接Logger和Recovery中间件。 router := gin.Default() // Gin 服务s1.用于运行HTTP服务器的参数 (常规参数) s1 := &http.Server{ // Gin运行的监听端口 Addr: ":8080", // 要调用的处理程序,http.DefaultServeMux如果为nil Handler: router, // ReadTimeout是读取整个请求(包括正文)的最长持续时间。 ReadTimeout: 5 * time.Second, // WriteTimeout是超时写入响应之前的最长持续时间 WriteTimeout: 10 * time.Second, // MaxHeaderBytes控制服务器解析请求标头的键和值(包括请求行)时读取的最大字节数 (通常情况下不进行设置) MaxHeaderBytes: 1 << 20, } // Go在一个新的goroutine中调用给定的函数,此处将Go语言的并发体现的淋漓尽致。 g.Go(func() error { return s1.ListenAndServe() }) // 配置Gin中间件 // Recovery返回一个中间件,该中间件可以从任何exception中恢复,并在出现exception时写入500。 router.Use(gin.Recovery()) // 服务s1的路由 router.GET("/", func(c *gin.Context) { c.JSON( http.StatusOK, gin.H{ "code": http.StatusOK, "msg": "Welcome server 01 www.weiyigeek.top", }, ) }) // Gin 服务s1.定义了不同的监听端口以及Handler s2 := &http.Server{ Addr: ":8081", Handler: router02(), ReadTimeout: 5 * time.Second, WriteTimeout: 10 * time.Second, } g.Go(func() error { return s2.ListenAndServe() }) if err := g.Wait(); err != nil { log.Fatal(err) } }
执行结果:
如何优雅的关闭或者重启Gin应用程序?
1.使用 chan 通道监听中断信号(SIGINT和SIGTERM)
描述: 在Go Gin中,可以使用以下代码实现优雅地重启或停止, 确保所有连接都被正确关闭,避免数据丢失或损坏。
代码示例:
package main import ( "context" "log" "net/http" "os" "os/signal" "syscall" "time" "github.com/gin-gonic/gin" ) func main() { // 创建 Gin 实例 router := gin.Default() // 添加路由 router.GET("/", func(c *gin.Context) { c.String(http.StatusOK, "Hello, World! weiyigeek.top") }) // 创建 HTTP Server srv := &http.Server{ Addr: ":8080", Handler: router, } // 开启一个goroutine启动服务 启动 HTTP Server go func() { if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatalf("listen: %s\n", err) } }() // 等待中断信号 quit := make(chan os.Signal) // kill 默认会发送 syscall.SIGTERM 信号 // kill -2 发送 syscall.SIGINT 信号,我们常用的Ctrl+C就是触发系统SIGINT信号 // kill -9 发送 syscall.SIGKILL 信号,但是不能被捕获,所以不需要添加它 // signal.Notify把收到的 syscall.SIGINT或syscall.SIGTERM 信号转发给quit signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) // 此处不会阻塞 <-quit // 阻塞在此,当接收到上述两种信号时才会往下执行 log.Println("Shutdown Server ...") // 创建一个 5 秒的超时上下文 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() // 关闭 HTTP Server // // 5秒内优雅关闭服务(将未处理完的请求处理完再关闭服务),超过5秒就超时退出 if err := srv.Shutdown(ctx); err != nil { log.Fatal("Server Shutdown:", err) } log.Println("Server exiting") }
代码解析:
首先创建了一个Gin实例和一个HTTP Server,然后启动HTTP Server。接下来,使用signal.Notify()
函数监听中断信号(SIGINT和SIGTERM),当接收到中断信号时,服务器会进入优雅关闭流程,即先关闭HTTP Server
,然后等待5秒钟,最后退出程序。
在关闭HTTP Server时,我们使用了srv.Shutdown()
函数,它会优雅地关闭HTTP Server并等待所有连接关闭。如果在5秒钟内没有关闭完所有连接,函数会返回错误。
知识补充:
使用os/signal包实现对信号的处理, 最常见的信号列表。
2.使用 os/exec 包来执行Gin平滑重启
描述: 在Linux的Go-gin环境中我们可以使用 os/exec 包来执行重启命令,然后在 Gin 中定义一个路由,使得访问该路由时会执行重启命令。
代码示例:
package main import ( "fmt" "net/http" "os" "os/exec" "github.com/gin-gonic/gin" ) func main() { r := gin.Default() // 重启的路由 /restart r.GET("/restart", func(c *gin.Context) { cmd := exec.Command("killall", "-HUP", "appweiyigeek") err := cmd.Run() if err != nil { fmt.Println("Error executing restart command:", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to restart Gin server."}) return } c.JSON(http.StatusOK, gin.H{"message": "Gin server restarted successfully."}) }) r.Run(":8080") }
编译执行:
go build ./main.go -o appweiyigeek ./appweiyigeek
在上面的例子中,我们定义了一个路由 /restart,当访问该路由时,它会执行 killall -HUP appweiyigeek
命令来重启 Gin 服务, 这里的appweiyigeek
应该替换为你实际的 Gin 应用程序的名称。
温馨提示: 此种重启方式可能会导致请求失败或者超时,因为它会强制关闭正在处理的连接, 如果你需要更加优雅的重启方式,可以考虑使用优雅重启的方式。
3.使用 fvbock/endless 包实现访问指定路由平滑重启Gin服务
描述: 由于endless在windows环境是不支持,所以博主针对下述代码在Linux环境下载并编译成二进制文件打包到Linux环境运行进行验证。
依赖下载:
go get -u github.com/fvbock/endless go mod tidy
代码示例:
package main import ( "fmt" "log" "net/http" "os/exec" "strconv" "syscall" "github.com/fvbock/endless" "github.com/gin-gonic/gin" ) func main() { pid := syscall.Getpid() // 1.默认的Gin引擎 router := gin.Default() // 传统方式 // server := &http.Server{ // Addr: ":8080", // Handler: router, // ReadTimeout: 5 * time.Second, // WriteTimeout: 10 * time.Second, // } // 2.获取 Pid router.GET("/pid", func(c *gin.Context) { pid = syscall.Getpid() fmt.Println("Pid:", pid) c.JSON(http.StatusOK, gin.H{ "code": http.StatusOK, "msg": fmt.Sprintf("Gin Server Pid -> %d.", pid), }) }) // 3.重启 Gin 服务 router.POST("/restart", func(c *gin.Context) { pid = syscall.Getpid() fmt.Println("Restarting Gin Server.......", pid) err := exec.Command("kill", "-1", strconv.Itoa(pid)).Run() if err != nil { fmt.Println("Error executing restart command:", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to restart Gin server."}) return } c.JSON(http.StatusOK, gin.H{"message": "Gin server restarted successfully.", "pid": pid}) }) // 4.使用endless侦听TCP网络地址addr,然后使用处理程序调用Serve来处理传入连接上的请求 err := endless.ListenAndServe(":8080", router) if err != nil || err != http.ErrServerClosed { log.Println("err:", err) } // 5.引入了endless扩展,将原本的Run方式启动项目改成了ListenAndServe方式所有此处主席掉 // router.Run(":8080") }
编译构建:
# 切换编译在Linux平台的64位可执行程序环境 go env -w CGO_ENABLED=0 GOOS=linux GOARCH=amd64 # 编译 go build -o endless-test-1 .\main.go # 执行验证 chmod +x endless-test-1 nohup ./endless-test-1 & [1] 1147978
执行效果:
# GET 请求 10.20.176.101:8080/pid
# POST 请求 10.20.176.101:8080/restart
请求restart
后可以看见go-gin已经平滑重启了是不是很方便,效果如下。
以上就是Go Web开发之Gin多服务配置及优雅关闭平滑重启实现方法的详细内容,更多关于Go Web Gin多服务配置的资料请关注脚本之家其它相关文章!
最新评论