zl程序教程

您现在的位置是:首页 >  其他

当前栏目

一次golang程序无故频繁重启的问题

2023-02-18 16:44:43 时间

1. 错误日志

监控日志

程序启动时会打印进程号,同时有系统signal信号捕捉程序,会将程序退出的所有能捕捉的信号都捕捉并打印,然后退出。

1.1不能被捕捉的信号

SIGKILL 9 Term 无条件结束程序(不能被捕获、阻塞或忽略) SIGSTOP 17,19,23 Stop 停止进程(不能被捕获、阻塞或忽略)

第一个就是我们常见的kill -9 pid

2. 排查方式

1.1 查看日志

日志中加入了捕捉信号量的程序,会将程序退出的所有能捕捉的信号都捕捉并打印,然后退出

// for build -ldflags
var (
    // explain for more details: https://colobu.com/2015/10/09/Linux-Signals/
    // Signal selection reference:
    //   1. https://github.com/fvbock/endless/blob/master/endless.go
    //   2. https://blog.csdn.net/chuanglan/article/details/80750119
    hookableSignals = []os.Signal{
        syscall.SIGHUP,
        syscall.SIGUSR1,
        syscall.SIGUSR2,
        syscall.SIGINT,
        syscall.SIGTERM,
        syscall.SIGTSTP,
        syscall.SIGQUIT,
        syscall.SIGSTOP,
        syscall.SIGKILL,
    }
    defaultHeartbeatTime = 1 * time.Minute
)

func handleSignal(pid int) {
    // Go signal notification works by sending `os.Signal`
    // values on a channel. We'll create a channel to
    // receive these notifications (we'll also make one to
    // notify us when the program can exit).
    sigs := make(chan os.Signal, 1)
    done := make(chan bool, 1)
    // `signal.Notify` registers the given channel to
    // receive notifications of the specified signals.
    signal.Notify(sigs, hookableSignals...)
    // This goroutine executes a blocking receive for
    // signals. When it gets one it'll print it out
    // and then notify the program that it can finish.
    go func() {
        sig := <-sigs
        logs.Info("pid[%d], signal: [%v]", pid, sig)
        done <- true
    }()
    // The program will wait here until it gets the
    // expected signal (as indicated by the goroutine
    // above sending a value on `done`) and then exit.
    for {
        logs.Debug("pid[%d], awaiting signal", pid)

        select {
        case <-done:
            logs.Info("exiting")
            return
        case <-time.After(defaultHeartbeatTime):
        }
    }

通过错误日志可以看到,捕捉不到,所以基本上可以推测,应该是信号SIGKILL或SIGSTOP的问题。

1.2 strace监控当前进程

命令:strace -T -tt -e trace=all -p pid,我当时监控时pid是39918,最后等待了一段时间,果然捕捉到了程序异常退出的信号

strace

但这只能确认这是SIGKILL的问题,具体什么原因,还是无法得知。

1.3 Taming the OOM killer

既然是SIGKILL,那很有可能是OOM的问题,所以我试了一下demsg命令:dmesg -T | grep -E -i -B100 pid,pid是39918。

dmesg

果然,真的是Out Of Memory 错误。

1.4 go tool pprof

我部署的web框架是beego,它本身自带嵌入了golang的调试程序,只需要在app.conf中设置AdminPort = 8098属性就可以,我设置的端口是8098。它还有个监控界面,可以直接在浏览器访问,还是很方便。

beego-admin

不过由于服务器在云端,没有开通这个端口,开通要走很多程序,麻烦,那算了,直接监控了把文件下载到本地查看,命令curl http://localhost:8098/prof?command=get%20memprof,之后,在你的程序的根目录下,就会生成一个mem-xxxx.memprof的文件。

memprof

把这个文件下载到本地,使用命令go tool pprof bdms mem-43964.memprof查看。后来果然发现一个程序没有关闭sql连接,导致大量的内存占用。pprof的教程我就不贴了,推荐一个链接自己看吧。今天就到这里吧。