问题出现
出现报警!!!
在日常搬砖的某一天发现了某微服务 bytedance.xiaoming 服务有一些实例内存过高,达到 80%。而这个服务很久没有上线过新版本,所以可以排除新代码上线引入的问题。
发现问题后,首先进行了迁移实例,除一台实例留作问题排查外,其余实例进行了迁移,迁移过后新实例内存较低。但发现随着时间推移,迁移过的实例内存也有缓慢增高的现象,有内存泄漏的表现。
问题定位
推测一:怀疑是 goroutine 逃逸
排查过程
通常内存泄露的主因就是 goroutine 过多,因此首先怀疑 goroutine 是否有问题,去看了 goroutine 发现很正常,总量较低且没有持续增长现象。(当时忘记截图了,后来补了一张图,但是 goroutine 数量一直是没有变化的)
排查结果
没有 goroutine 逃逸问题。
推测二:怀疑代码出现了内存泄露
排查过程
通过 pprof 进行实时内存采集,对比问题实例和正常实例的内存使用状况:
问题实例:
正常实例:
进一步看问题实例的 graph:
从中可以发现,metircs.flushClients()占用的内存是最多的,去定位源码:
func (c *tagCache) Set(key []byte, tt *cachedTags) {
if atomic.AddUint64(&c.setn, 1)&0x3fff == 0 {
// every 0x3fff times call, we clear the map for memory leak issue
// there is no reason to have so many tags
// FIXME: sync.Map don’t have Len method and setn
may not equal to the len in concurrency env
samples := make([]interface{}, 0, 3)
c.m.Range(func(key interface{}, value interface{}) bool {
c.m.Delete(key)
if len(samples) < cap(samples) {
samples = append(samples, key)
}
return true
}) // clear map
logfunc(“[ERROR] gopkg/metrics: too many tags. samples: %v”, samples)
}
c.m.Store(string(key), tt)
}
发现里面为了规避内存泄露,已经通过计数的方式,定数清理掉