一 项目问题
某数据库服务器出现数据同步降速的情况,影响业务正常运行,排查发现256G内存仅有2G左右的空闲内存。
二 内存占用分析
起初通过top分析内存占用情况可知dmserver驻留的物理内存较高:
图 一
同时通过pmap分析dmserver进程的内存分布情况,可以确认数据进程大部分内存占用于anon(匿名页):
图 二
分析过程中,现场技术人员告知存在节点未起dmserver,内存也持续增长的情况。故怀疑系统本身或者其他应用可能存在内存泄露的情况,针对当前系统走查了可能存在内存泄露的几个基本组件,发现对应组件本身占用内存正常,并无内存异常增长的情况。然后分析/proc/meminfo的分布情况:
图 三
针对内存泄露的分析核心在于匿名页的分配(malloc和mmap),以及内核态slab内存缓存块分配器的变化情况,经过持续观察并未发现anonpage和slab有明显变化。但是active file却有缓慢变化且占用最多,这意味着文件页持续增加,可能存在进程读写文件时导致pagecache持续递增。
pagecache的设计存在于vfs层,针对每个文件都有自己的缓存,以页为单位,这样可以避免读写文件时直接访问物理设备,提升读写速率。
内核inode索引节点的成员i_mapping指向address_space(文件地址结构空间结构体),同时file实例结构体成员f_mapping也会指向address_sapce。可以理解address_space中存着每个文件pagecache的存储结构(基数树)。
以下为address_space的结构体信息:
图 四
成员host指向索引节点
成员i_pages为基数树结构
成员nr_pages为文件地址空间对应的pagecache总和
针对pagecache的添加内核提供对应了函数接口:
int add_to_page_cache_lru(struct page *page, struct address_space *mapping,
pgoff_t offset, gfp_t gfp_mask)
这里可以利用bpftrace工具编写针对add_to_page_cache_lru函数的kprobe,然后输出对应address_space中pagecache总和:
bpftrace脚本如下:
#!/usr/bin/bpftrace
#include<linux/fs.h>
BEGIN
{
printf("Tracing page cache add... 通过 Ctrl-C结束.\n");
printf("PID COMM PAGENUM \n");
}
kprobe:add_to_page_cache_lru
{
$address=(struct address_space *)arg1;
/*获取pagecache数量总和*/
$page_num=$address->nrpages;
/获取inod索引编号/
$ino=$address->host->i_ino;
/*通过定义page数组,统计进程以及pagecache数量和函数调用次数*/
printf("%d %s %d %ld\n",pid,comm,($page_num+1),$ino);
}
END
{
printf("bye!");
}
图 五
运行对应脚本,追踪文件地址空间中当pagecache数量大于10000,输出进程信息,结果如下:
图 六
进程号2300,进程rs:main(rsyslog线程),pagecache数量1341243,同时还在持续增加。已知页大小为65536,通过1341243*65536/1024/1024/1024=81,说明该进程打开对应文件的文件页就有81G。尝试重启rsyslog服务,cache释放了90G左右。通过strace 追踪rsyslog线程的系统调用:
图 七
持续写入文件描述为6的文件,查看描述符对应文件(/proc/pid/fd):
图 八
可以确定rsyslog进程一直在写入不存在的文件(auth.log-20220319),说明真实文件已经不存在,但是文件描述以及文件实例并未回收。同步通过bpftrace追踪vfs_write调用:
图 九
指定rsyslog进程监控,输出调用vfs_write次数,以及写入文件对象,收集10s结果如下:
图 十
ps:因为是在数据库集群中出现的问题,故多台机器上抓取数据,可能存在截图内容差异的地方
10s内重复写入delete文件32次,故确认问题为rsyslog本身的BUG:
rsyslog操作delete文件时,内核态文件实例并未关闭,对应文件地址空间也不会释放,在写入缓存时依旧持续分配pagecache(成功调用 add_to_page_cache_lru),导致cache持续增加。