Go-gin-example 第四部分 重启服务


续接 上一节

知识点

  • 信号量的了解
  • 应用热更新

本节目标

在前文中,我们在配置玩之后直接使用 ctrl+c 来进行进程的结束,我们将了解 ctrl+c 的过程中到底进行了什么
简单讲述 ctrl+c 背后的信号 以及如何在gin优雅的重启服务,也就是对HTTP服务进行热更新

何谓优雅

  • 不关闭现有连接(正在运行中的程序)
  • 新的进程启动并替代旧进程
  • 新的进程接管新的连接
  • 连接要随时相应用户的请求,当用户仍在请求旧进程时要保持连接,新用户应请求新进程,不可以出现拒绝请求的情况

ctrl+c

内核在某些情况下发送信号,比如在进程往一个已经关闭的管道写数据时会产生SIGPIPE信号
我们在执行ctrl+c关闭gin服务端时,会强制结束进程,导致正在访问的用户等出现问题

信号

信号Unix类 Unix 以及其他POSIX兼容的操作系统中进程间通讯的一种有限制的方式

它是一种异步的通知机制,用来提醒进程一个事件(硬件异常、程序执行异常、外部发出信号)已经发生。当一个信号发送给一个进程,操作系统中断了进程正常的控制流程。此时,任何非原子操作都将被中断。如果进程定义了信号的处理函数,那么它将被执行,否则就执行默认的处理函数

修改流程

  1. 替换可执行文件或修改配置文件
  2. 发送信号量
  3. 拒绝新连接请求旧进程,但要保证已有连接进程
  4. 去启动新的子进程
  5. 新的子进程开始accept
  6. 系统将新的请求转交新的子进程
  7. 旧进程处理玩所有旧连接后正常结束

实现优雅重启

我们借助 fvbock/endless 来实现 Golang HTTP/HTTPS 服务重新启动的零停机

endless

借助 fvbock/endless 来实现 Golang HTTP/HTTPS 服务重新启动的零停机

endless server监听以下几种信号量:

  • syscall.SIGHUP:触发 fork 子进程和重新启动
  • syscall.SIGUSR1/syscall.SIGTSTP:被监听,但不会触发任何动作
  • syscall.SIGUSR2:触发 hammerTime
  • syscall.SIGINT/syscall.SIGTERM:触发服务器关闭(会完成正在运行的请求)

安装

go get -u github.com/fvbock/endless

编写

打开 gin-blogmain.go文件,修改文件:

...
func main() {

	/*	之前的版本
		//将原来创建的默认router和建立对应的handler绑定
			router := routers.InitRouter()

			//创建一个http服务器,将前面的router绑定为这里的处理器
			s := &http.Server{
				Addr:           fmt.Sprintf(":%d", setting.HTTPPort),
				Handler:        router,
				ReadTimeout:    setting.ReadTimeout,
				WriteTimeout:   setting.WriteTimeout,
				MaxHeaderBytes: 1 << 20,
			}

			//启动服务器
			s.ListenAndServe()
	*/

	//当前版本,建议对比源代码进行理解
	//进行配置的导入,在setting包中进行设置
	endless.DefaultReadTimeOut = setting.ReadTimeout
	endless.DefaultWriteTimeOut = setting.WriteTimeout
	endless.DefaultMaxHeaderBytes = 1 << 20
	endPoint := fmt.Sprintf(":%d", setting.HTTPPort)

	//newsever返回一个初始化的endlessServer对象
	server := endless.NewServer(endPoint, routers.InitRouter())
	//输出当前进程的PID
	server.BeforeBegin = func(add string) {
		log.Printf("Actual pid is %d", syscall.Getpid())
	}

	//启动服务
	err := server.ListenAndServe()
	if err != nil {
		log.Printf("Server err: %v", err)
	}

}

验证

编译

$ go build main.go

执行

$ ./main
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /auth                     --> github.com/kingsill/gin-example/routers/api.GetAuth (3 handlers)
[GIN-debug] GET    /api/v1/tags              --> github.com/kingsill/gin-example/routers/api/v1.GetTags (4 handlers)
[GIN-debug] POST   /api/v1/tags              --> github.com/kingsill/gin-example/routers/api/v1.AddTag (4 handlers)
[GIN-debug] PUT    /api/v1/tags/:id          --> github.com/kingsill/gin-example/routers/api/v1.EditTag (4 handlers)
[GIN-debug] DELETE /api/v1/tags/:id          --> github.com/kingsill/gin-example/routers/api/v1.DeleteTag (4 handlers)
[GIN-debug] GET    /api/v1/articles          --> github.com/kingsill/gin-example/routers/api/v1.GetArticles (4 handlers)
[GIN-debug] GET    /api/v1/articles/:id      --> github.com/kingsill/gin-example/routers/api/v1.GetArticle (4 handlers)
[GIN-debug] POST   /api/v1/articles          --> github.com/kingsill/gin-example/routers/api/v1.AddArticle (4 handlers)
[GIN-debug] PUT    /api/v1/articles/:id      --> github.com/kingsill/gin-example/routers/api/v1.EditArticle (4 handlers)
[GIN-debug] DELETE /api/v1/articles/:id      --> github.com/kingsill/gin-example/routers/api/v1.DeleteArticle (4 handlers)
2024/03/04 20:08:31 Actual pid is 593

可以看到我们当前的进程pid593,在另一个终端执行kill -1 48601

2024/03/04 20:09:21 593 Received SIGHUP. forking.
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /auth                     --> github.com/kingsill/gin-example/routers/api.GetAuth (3 handlers)
[GIN-debug] GET    /api/v1/tags              --> github.com/kingsill/gin-example/routers/api/v1.GetTags (4 handlers)
[GIN-debug] POST   /api/v1/tags              --> github.com/kingsill/gin-example/routers/api/v1.AddTag (4 handlers)
[GIN-debug] PUT    /api/v1/tags/:id          --> github.com/kingsill/gin-example/routers/api/v1.EditTag (4 handlers)
[GIN-debug] DELETE /api/v1/tags/:id          --> github.com/kingsill/gin-example/routers/api/v1.DeleteTag (4 handlers)
[GIN-debug] GET    /api/v1/articles          --> github.com/kingsill/gin-example/routers/api/v1.GetArticles (4 handlers)
[GIN-debug] GET    /api/v1/articles/:id      --> github.com/kingsill/gin-example/routers/api/v1.GetArticle (4 handlers)
[GIN-debug] POST   /api/v1/articles          --> github.com/kingsill/gin-example/routers/api/v1.AddArticle (4 handlers)
[GIN-debug] PUT    /api/v1/articles/:id      --> github.com/kingsill/gin-example/routers/api/v1.EditArticle (4 handlers)
[GIN-debug] DELETE /api/v1/articles/:id      --> github.com/kingsill/gin-example/routers/api/v1.DeleteArticle (4 handlers)
2024/03/04 20:09:21 Actual pid is 699
2024/03/04 20:09:21 593 Received SIGTERM.
2024/03/04 20:09:21 593 Waiting for connections to finish...
2024/03/04 20:09:21 593 Serve() returning...
2024/03/04 20:09:21 Server err: accept tcp [::]:8000: use of closed network connection

可以看到该命令已经挂起,并且fork了新的进程pid为699

唤醒

这时候在 postman 上再次访问我们的接口

╭─  │  ~/gin-example │   main ?1 ········································· ✔ │ 50s  │ 08:09:21 PM 
╰─
(/home/wang2/gin-example/models/article.go:45)
[2024-03-04 20:09:45]  [0.67ms]  SELECT * FROM `blog_article`   LIMIT 10 OFFSET 0
[0 rows affected or returned ]

postman继续访问时,当前终端从命令行重新进入程序的执行过程中
这就完成了一次正向的流转了

你想想,每次更新发布、或者修改配置文件等,只需要给该进程发送SIGTERM 信号,而不需要强制结束应用,是多么便捷又安全的事!

问题

endless 热更新是采取创建子进程后,将原进程退出的方式,这点不符合守护进程的要求

  • 16
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值