Go Web开发之Gin多服务配置及优雅关闭平滑重启实现方法

 更新时间:2024年01月31日 10:15:39   作者:WeiyiGeek 全栈工程师  
这篇文章主要为大家介绍了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多服务配置的资料请关注脚本之家其它相关文章!

相关文章

  • golang中select语句的简单实例

    golang中select语句的简单实例

    Go的select语句是一种仅能用于channl发送和接收消息的专用语句,此语句运行期间是阻塞的,下面这篇文章主要给大家介绍了关于golang中select语句的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2022-06-06
  • 详解Golang语言HTTP客户端实践

    详解Golang语言HTTP客户端实践

    本文主要介绍了Golang语言HTTP客户端实践,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-11-11
  • Go多线程中数据不一致问题的解决方案(sync锁机制)

    Go多线程中数据不一致问题的解决方案(sync锁机制)

    在Go语言的并发编程中,如何确保多个goroutine安全地访问共享资源是一个关键问题,Go语言提供了sync包,其中包含了多种同步原语,用于解决并发编程中的同步问题,本文将详细介绍sync包中的锁机制,需要的朋友可以参考下
    2024-10-10
  • Go语言学习教程之结构体的示例详解

    Go语言学习教程之结构体的示例详解

    结构体是一个序列,包含一些被命名的元素,这些被命名的元素称为字段(field),每个字段有一个名字和一个类型。本文通过一些示例带大家深入了解Go语言中结构体的使用,需要的可以参考一下
    2022-09-09
  • Golang使用Decimal库避免运算中精度损失详细步骤

    Golang使用Decimal库避免运算中精度损失详细步骤

    decimal是为了解决Golang中浮点数计算时精度丢失问题而生的一个库,使用decimal库我们可以避免在go中使用浮点数出现精度丢失的问题,下面这篇文章主要给大家介绍了关于Golang使用Decimal库避免运算中精度损失的相关资料,需要的朋友可以参考下
    2023-06-06
  • Go单元测试对GORM进行Mock测试

    Go单元测试对GORM进行Mock测试

    这篇文章主要为大家介绍了Go单元测试对GORM进行Mock测试用例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-06-06
  • Golang库插件注册加载机制的问题

    Golang库插件注册加载机制的问题

    这篇文章主要介绍了Golang库插件注册加载机制,这里说的插件并不是指的golang原生的可以在buildmode中加载指定so文件的那种加载机制,需要的朋友可以参考下
    2022-03-03
  • golang 字符串拼接方法对比分析

    golang 字符串拼接方法对比分析

    这篇文章主要为大家介绍了golang 字符串拼接方法对比分析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-09-09
  • go原子级内存操作实现

    go原子级内存操作实现

    原子级内存操作是在多线程并发执行时,能够确保某个内存操作是不可中断的操作,本文主要介绍了go原子级内存操作实现,具有一定的参考价值,感兴趣的可以了解一下
    2024-02-02
  • Go语言range关键字循环时的坑

    Go语言range关键字循环时的坑

    今天小编就为大家分享一篇关于Go语言range关键字循环时的坑,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2019-03-03

最新评论