记一次Golang服务优雅关闭

背景:最近工作中接了一个大活,一个日访问量一亿两千多次的微服务交到了我手上,但是在一次功能迭代后发布的时候出现问题了,这个服务每次线上发布容器滚动升级的时候都会收到一波503请求报警。

排查过程:

1.收到报警后查看日志,发现报警的容器ip都是滚动升级下掉的容器ip,想到可能是服务关闭时没有将请求处理完就关掉了,查看代码发现这个服务是没有优雅关闭这种处理的。所以得先把优雅关闭安排上。大概代码如下:


server := http.Server{
    Addr:    ":8081",
    Handler: engine,
}
//开启http服务
go func () {
    err := server.ListenAndServe()
    if err == nil {
        fmt.Println("error")
    }
}()
//等待接收服务关闭信号
c := make(chan os.Signal)
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
<-c
//接收到信号后设置最长等待时间为5s
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
//等待所有http请求处理结束后再关闭服务,最长等待时间不超过5秒
err := server.Shutdown(ctx)
if err != nil {
cancel()
}

复制代码

2.本地代码跑起来试了一波,发现优雅关闭已经生效,添加调试日志后发布到测试环境发现每次docker容器重启后日志并没有记录到接收到的关闭信号,去网上搜了一圈说Dockerfile里面启动服务的CMD命令需要用exec格式的(CMD命令格式说明如下所示),这样容器关闭时服务才能收到关闭信号。查看了一下代码里CMD是shell格式的,而本地优雅关闭生效时因为本地是直接用命令行跑起来的并不是使用docker容器跑起来的。

cmd命令的三种格式
CMD ["executable","param1","param2"] (exec form, this is the preferred form)
CMD ["param1","param2"] (as default parameters to ENTRYPOINT)
CMD command param1 param2 (shell form)
复制代码

3.改了CMD执行格式发现服务依然收不到关闭信号,又搜了一圈发现容器关闭时执行的docker stop命令只会把关闭信号传递给进程号是1的进程,进入测试环境容器ps -ef发现当前服务进程号竟然不是1,具体原因是因为当前服务在Dockerfile中是由一个进程管理的镜像跑起来的,而容器中的1号进程是进程管理的服务,当前业务服务只是进程管理服务的子进程,所以当前业务服务收不到关闭信号。于是改了一波Dockerfile,不使用进程管理服务来启动业务服务,直接启动业务服务使得业务进程为容器内1号进程。发到测试环境测试时业务进程接收到了关闭信号,这时优雅关闭才生效。

总结:
1.golang优雅关闭需要在代码中添加接收关闭信号后处理剩余http请求代码
2.docker微服务优雅关闭首先需要保证业务进程是容器内1号进程,这样才能收到关闭信号。
复制代码

猜你喜欢

转载自juejin.im/post/7081259982523465765