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,最后等待了一段时间,果然捕捉到了程序异常退出的信号
但这只能确认这是SIGKILL的问题,具体什么原因,还是无法得知。
1.3 Taming the OOM killer
既然是SIGKILL,那很有可能是OOM的问题,所以我试了一下demsg命令:dmesg -T | grep -E -i -B100 pid
,pid是39918。
果然,真的是Out Of Memory 错误。
1.4 go tool pprof
我部署的web框架是beego,它本身自带嵌入了golang的调试程序,只需要在app.conf中设置AdminPort = 8098
属性就可以,我设置的端口是8098。它还有个监控界面,可以直接在浏览器访问,还是很方便。
不过由于服务器在云端,没有开通这个端口,开通要走很多程序,麻烦,那算了,直接监控了把文件下载到本地查看,命令curl http://localhost:8098/prof?command=get%20memprof
,之后,在你的程序的根目录下,就会生成一个mem-xxxx.memprof的文件。
把这个文件下载到本地,使用命令go tool pprof bdms mem-43964.memprof
查看。后来果然发现一个程序没有关闭sql连接,导致大量的内存占用。pprof的教程我就不贴了,推荐一个链接自己看吧。今天就到这里吧。