Golang | Web开发之Gin多服务配置及优雅关闭平滑重启

欢迎关注「全栈工程师修炼指南」公众号

点击 👇 下方卡片 即可关注我哟!

设为星标⭐每天带你 基础入门 到 进阶实践 再到 放弃学习

专注 企业运维实践、网络安全、系统运维、应用开发、物联网实战、全栈文章 等知识分享

  花开堪折直须折,莫待无花空折枝 


作者主页:[ https://www.weiyigeek.top ]  

博客:[ https://blog.weiyigeek.top ]

作者<安全开发运维>学习交流群,回复【学习交流群】即可加入


文章目录:

0x00 如何自定义Gin服务配置及其启动多个服务?

0x01 如何优雅的关闭或者重启Gin应用程序?

1.使用 chan 通道监听中断信号(SIGINT和SIGTERM)

2.使用 os/exec 包来执行Gin平滑重启

3.使用 fvbock/endless 包实现访问指定路由平滑重启Gin服务

0x00 如何自定义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)
	}
}

偷偷的告诉你哟?极客全栈修炼】微信小程序已经上线了,

可直接在微信里面直接浏览博主博客了哟,后续将上线更多有趣的小工具。


执行结果:

9b29c34347d2eba226b503385be130b5.png


0x01 如何优雅的关闭或者重启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包实现对信号的处理, 最常见的信号列表。

bcbc58ee17922779c92a699099e0978f.png

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

2a959d90cb6844ac6b72df3427d705a9.png

执行效果:

# GET 请求 10.20.176.101:8080/pid
# POST 请求 10.20.176.101:8080/restart

33299088e0978487543abd7840a49921.png

请求restart后可以看见go-gin已经平滑重启了是不是很方便,效果如下。

62f355d47ca812955c92095ebe610e3e.png

亲,文章就要看完了,不关注一下【全栈工程师修炼指南】吗?

知识扩展:

  • 3、Windows下编译Mac, Linux平台的64位可执行程序:

$ go env -w CGO_ENABLED=0 GOOS=darwin3 GOARCH=amd64 
$ go env -w CGO_ENABLED=0 GOOS=linux GOARCH=amd64
  • 2、Linux下编译Mac, Windows平台的64位可执行程序:

$ go env -w CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 
$ go env -w CGO_ENABLED=0 GOOS=windows GOARCH=amd64
  • 1、Mac下编译Linux, Windows平台的64位可执行程序:

$ go env -w CGO_ENABLED=0 GOOS=linux GOARCH=amd64
$ go env -w CGO_ENABLED=0 GOOS=windows GOARCH=amd64

本文至此完毕,更多技术文章,尽情等待下篇好文!

原文地址: https://blog.weiyigeek.top/2023/6-2-745.html

如果此篇文章对你有帮助,请你将它分享给更多的人! 

b9b9f576743861eeccc707bd9f905e2a.gif

01adbdb6830230caebe2ed3c8f28b232.png 学习书籍推荐 往期发布文章 01b09483f7f760de481abcb1ac57fa1c.png

公众号回复【0008】获取【Ubuntu22.04安装与加固脚本】

公众号回复【10001】获取【WinServer安全加固脚本】

公众号回复【10002】获取【KylinOS银河麒麟安全加固脚本】

公众号回复【0011】获取【k8S二进制安装部署教程】

公众号回复【0014】获取【Nginx学习之路汇总】

公众号回复【0015】获取【Jenkins学习之路汇总】

公众号回复【10005】获取【adb工具刷抖音赚米】

 热文推荐  

欢迎长按(扫描)二维码 取更多渠道哟!

欢迎关注 【全栈工程师修炼指南】(^U^)ノ~YO

添加作者微信【weiyigeeker 】 一起学习交流吧!

关注回复【学习交流群】即可加入【安全运维沟通交流小群

温馨提示: 由于作者水平有限,本章错漏缺点在所难免,希望读者批评指正,若有问题或建议请在文章末尾留下您宝贵的经验知识,或联系邮箱地址

master@weiyigeek.top 或 关注公众号 [全栈工程师修炼指南] 留言。

点个【赞 + 在看】吧!

点击【"阅读原文"】获取更多有趣的知识!   

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值