本文主要介绍如何优雅的重启或平滑的重启Golang服务,在这里用到的就是endless。
完整库路径是:github.com/fvbock/endless
一、应用场景
每次修改完Go程序需要重新编译然后发布到服务器上,那么重启服务时应该怎么做,如果终止以前的进程,在重新启动,在这个时间间隔里服务是不可用的,这样是不能接受的。那么用两台服务器轮流重启,先重启一台后再重启另外一台,这样服务就一直是可用的,但是这样也有问题,就是你强制终止旧的进程,旧的进程在执行过程中中断,导致数据出现错误,这样也是不能的接收的。
我们要做到的优雅重启要做到:
1. 不中断正在执行中的程序
2. 服务始终保持可用
3. 旧的进程处理完后自动退出,新的请求过来由新的进程处理
二、实验过程
1. 第一次请求的代码
package main
import (
"github.com/fvbock/endless"
"github.com/gin-gonic/gin"
"time"
)
func main() {
r := gin.Default()
r.GET("/test", handler)
s := endless.NewServer(":8081", r)
s.ReadHeaderTimeout = 3 * 60 * time.Second
s.WriteTimeout = 3 * 60 * time.Second
s.MaxHeaderBytes = 1 << 20
s.ListenAndServe()
}
func handler(c *gin.Context) {
//第一次请求中休眠2分钟再返回 给操作留下足够时间
time.Sleep(time.Second * 60 * 2)
c.JSON(200, gin.H{"p": "第一次请求"})
}
2. 编译代码并执行
go build -o endless
./endless &
3. 查看此时的进程ID为38911
4. 发起第一次请求会等待2分钟后返回
curl 'http://127.0.0.1:8081/test'
5. 此时修改代码并且编译好
package main
import (
"github.com/fvbock/endless"
"github.com/gin-gonic/gin"
"time"
)
func main() {
r := gin.Default()
r.GET("/test", handler)
s := endless.NewServer(":8081", r)
s.ReadHeaderTimeout = 3 * 60 * time.Second
s.WriteTimeout = 3 * 60 * time.Second
s.MaxHeaderBytes = 1 << 20
s.ListenAndServe()
}
func handler(c *gin.Context) {
//第一次请求中休眠2分钟再返回 给操作留下足够时间
//time.Sleep(time.Second * 60 * 2)
c.JSON(200, gin.H{"p": "第二次请求"})
}
6. 重启服务
kill -1 38911
7. 发起第二次请求直接返回了结果
8. 查看endless已经启动了两个进程
其中38911为旧的正在处理第一次请求
39965为新的服务会处理新的请求
9. 2分钟后第一次请求返回了结果
10. 此时再次查看进程就剩下一个新的进程了
这样就完美实现了服务的优雅重启,并且不会中断服务,也不会影响重启前正在执行的请求。
回过头来我们服务的启动和重启过程
第一次启动 pid=38911 端口=8081
执行kill -1 38911重启服务,39965为新启动的服务,最后38911关闭。
注:个人的理解有限,如果有不正确的地方欢迎大家指正讨论。