项目要求要求开发一个agent来监控主机的性能,这个agent最好是编译好后能直接在主机上运行,所以选择了用go来编写。由于第一次用go,这里面踩了不少坑。
内存泄漏
当agent部署到主机上的时候,运行一段时间就停掉了,而且还没有错误提示,最后在阿里云这个主机的监控图形中看到,这个主机的内存是慢慢向上的,等内存使用率接近100%的时候,内存突然降了下来,当时就怀疑当内存使用率接近100%的时候,agent就会被停掉,然后内存得以释放。
利用linux命令找到这个agent的内存变化,发现随着时间的增长,它的内存是不断上升的,和前面的怀疑是一致的,查找到agent的进程id,然后用 top -p 12440 ,12440是进程的进程id。
ps -ef|grep cloudMonitor
top -p 12440
这个agent是利用定时任务将监控到的数据传输到其他工程,当时就怀疑是定时任务导致的内存泄漏,在网上查找go的time.NewTicker也会出现很多说当不调用t.top就会造成内存泄漏,我当时确实没有defer t.Stop()这行代码,加上这行代码后,改造成了下面的代码,:
func main() {
durationTime := 2*time.Second
t := time.NewTicker(durationTime)
defer t.Stop()
for{
<-t.C
fmt.Println("执行业务逻辑,发送http请求")
}
}
满以为问题得到解决,但是过段时间后,发现内存还是在不断升高,问题没有解决,最后想到了java的发送hhtp请求后一般都会调用close方法的,一检查发现go发送http请求的时候没有调用close方法,后面再到网上查找,有人确实出现过发送http请求后,没有调用close方法导致内存泄漏的,最后再在发送完http请求后加上了defer resp.Body.Close()
reqCountBytes, err :=json.Marshal(obj) // 把请求结构体解析为json
if err != nil {
log.Info("marshal failed. the error info: ",err)
}
log.Info("==================分割线===================")
log.Info(string(reqCountBytes))
//"application/json"
resp,err := http.Post(url,"application/json", bytes.NewReader(reqCountBytes)) // 调用rest接口
if err != nil {
log.Info("发送数据推送报错:",err)
}
defer resp.Body.Close()
最后启动agent监控agent内存的使用率,发现agent的内存使用率没怎么变化过。
go有点和java不一样的就是,当如果不对go的异常进行捕获的话,go会退出运行,也就是进程会死掉。这个agent利用定时任务获取主机的信息然后利用http将信息传输到数据中心,如果数据中性挂掉的话,这个agent由于http请求被拒绝也会挂掉,这样就很不好了,因为如果我改了数据中心的代码需要重启,那agnet都会挂掉,然后等数据中心重启完成后,再重启agent,这样明显不合理,所以这里需要对go抛出的异常进行捕获,够没有像java那样有try cach ,但go有panic,defer,recover,这里的panic就等于java中throws的作用,defer就等于java中finally的作用,代码改造成下面的样子:
idleDuration := time.Duration(num) * time.Second
idleDelay := time.NewTimer(idleDuration)
defer idleDelay.Stop()
for {
func() {
idleDelay.Reset(idleDuration)
defer func() {
if r := recover(); r != nil {
log.Errorf("捕获到的错误:%s\n", r)
}
}()
select {
case <-idleDelay.C:
sendUrlData(name,cloudType,url,num)
}
}()
}
这算是水了一篇博客,主要是弄这个agent弄了很久,感觉今天才算完善的差不多了。以上就是我遇到的一些比较大的问题,并得到了解决。