1 问题现象
在linux内核pagesize为64K配置,部署运行ceph软件,设置osd进程的内存上限为2G,在长时间的读写情况下,发现ceph-osd的内存占用会超过2G,不会被回收。如下图所示。
图1 ceph-osd进程内存占用超过2G的限制
2 原因分析
通过导出osd的heap的信息,如下图,从图中可以看出,use by application是2G左右,由于osd有内存上限2G的设置,符合预期。page heap freelist为4G,这部分内存是osd释放交还给tcmalloc的内存,是可以被tcmalloc分配给osd使用或者释放给os的。
图2 ceph-osd heap状态
tcmalloc中的page heap freelist的内存会被定期释放给os,观测了很久也没看见page heap freelist的内存减少。并且ceph osd中实现了释放的接口,ceph daemon osd.97 heap release可以直接释放,也没有效果。分析tcmalloc的内存释放流程,释放过程的运行栈如下图所示。最终释放内存给os是在TCMalloc_SystemRelease中通过madvise释放给os的。
图3 osd中tcmalloc内存释放运行栈
从PageHeap::ReleaseAtLeastNPages的实现看,此函数主要是按页进行内存的释放,直到完成了num_pages页的释放就停止。每次释放都是接着上一次的release_index_进行的。但是第484行,当released_len == 0时,就直接退出了,并不会继续循环了,此时release_index_也不会更新了,下一次释放还是从release_index_开始,下次还是直接退出了。内存就无法释放给os了。接着往下分析什么情况下released_len == 0。
图4 PageHeap::ReleaseAtLeastNPages的实现
从TCMalloc_SystemRelease的实现看,输入需要释放的地址和长度。释放之前需要对齐到页。只有一个完整的页才能被释放,当length小于一页的时候,是不会释放的。上图中x1表示length=32768,不会被释放。这种情况在内核pagesize为64K,tcmalloc page size小于64K(默认8K)下,很容易出现。在内核pagesize为4K的情况下就不会出现此种情况。
图5 TCMalloc_SystemRelease的实现
3 解决方案
在PageHeap::ReleaseAtLeastNPages中忽略掉released_len为0的情况,继续运行(当然也可以在released_len为0时,release_index_++,然后再退出,但是与函数实现的意图不太相符),修改代码如下:
Length PageHeap::ReleaseAtLeastNPages(Length num_pages) {
Length released_pages = 0;
int count = 0;
// Round robin through the lists of free spans, releasing the last
// span in each list. Stop after releasing at least num_pages
// or when there is nothing more to release.
while (released_pages < num_pages && stats_.free_bytes > 0) {
for (int i = 0; i < kMaxPages+1 && released_pages < num_pages;
i++, release_index_++, count++) {
if (release_index_ > kMaxPages) release_index_ = 0;
SpanList* slist = (release_index_ == kMaxPages) ?
&large_ : &free_[release_index_];
if (!DLL_IsEmpty(&slist->normal)) {
Length released_len = ReleaseLastNormalSpan(slist);
// Some systems do not support release
//if (released_len == 0) return released_pages;
released_pages += released_len;
}
}
if (count == kMaxPages+1)
break;
}
return released_pages;
}
# 直接用这个包,这里面已经合入了修复代码
wget https://github.com/gperftools/gperftools/releases/download/gperftools-2.8/gperftools-2.8.tar.gz --no-check-certificate