本文主要介绍如何优雅的重启或平滑的重启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

bgp 优雅重启与非优雅重启的区别 go 优雅重启_服务器

 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. 发起第二次请求直接返回了结果

bgp 优雅重启与非优雅重启的区别 go 优雅重启_重启_02

8. 查看endless已经启动了两个进程

其中38911为旧的正在处理第一次请求

39965为新的服务会处理新的请求

bgp 优雅重启与非优雅重启的区别 go 优雅重启_bgp 优雅重启与非优雅重启的区别_03

9. 2分钟后第一次请求返回了结果

bgp 优雅重启与非优雅重启的区别 go 优雅重启_重启_04

10. 此时再次查看进程就剩下一个新的进程了

bgp 优雅重启与非优雅重启的区别 go 优雅重启_golang_05

这样就完美实现了服务的优雅重启,并且不会中断服务,也不会影响重启前正在执行的请求。

回过头来我们服务的启动和重启过程

第一次启动 pid=38911 端口=8081

bgp 优雅重启与非优雅重启的区别 go 优雅重启_github_06

 执行kill -1 38911重启服务,39965为新启动的服务,最后38911关闭。

bgp 优雅重启与非优雅重启的区别 go 优雅重启_重启_07

注:个人的理解有限,如果有不正确的地方欢迎大家指正讨论。