influxdb内存占用大的原因分析(源码篇)

2024-09-11  本文已影响0人  数齐

背景

influxdb是一款开源的高性能时序数据库。在我们SEE监控模块是必备组件,主要用于帮助我们快速地存储、查询和分析这些数据。虽然我们享受到influxdb带来的诸多益处,但是很多客户会反馈相较于其他应用influxdb的内存占用实在是太大了,今天我们进来系统的分析一下。

分析过程

通过TOP命令,分析内存占用的总体情况

我们可以看到influxdb这个进程中目前VIRT是15G,RES是6.2G,SHR是6.1G。那么我们需要弄清楚这三个指标分别代表了什么

VIRT:一个进程地址空间的总大小,包括代码段、数据段(包括堆和栈)、共享库、映射文件等。它反映了进程可寻址的虚拟内存空间大小。也就是是程序向操作系统申请的空间大小,但是不一定会完全被占用,只是一个理论值。

RES:  指的是进程当前在RAM中使用的内存大小,也就是常驻内存的大小。RES包括了所有的程序指令、数据以及共享库等在内,但不包括被交换出去的内存。

SHR: 表示共享内存或共享库的大小。在 Linux 系统中,共享内存是多个进程可以同时访问的一段内存区域,多个进程可以通过映射同一个共享内存区域来实现进程间通信。而共享库则是多个进程可以共享的动态链接库,在内存中只需要加载一份共享库,就可以在多个进程中重复使用,从而减少了内存的占用。

RES=SHR+程序自身内存占用(堆栈相关内存占用),通过上图的TOP来看,常驻内存中大部分都是共享内存,那么到底共享了什么?

通过smaps,确定共享内存中的内容

通过查看/proc/pid/smaps来确定具体的内存占用

我们主要关注里面的Size、Rss字段,分别代表了申请的内存空间的大小以及真实占用内存的大小,发现那些占用大的内存快,基本上来自xxx.tsm文件,可能通过下面的方式看起来更直观一点:

cat /proc/228347/smaps | sed -n '/see/,+2p' | grep -v 'Size:' | sed 's/\ kB//' | awk '{print $NF}' | awk 'BEGIN{i=-1}{i++;a[i]=$0}END{for(k=0;k<length(a);k=k+2) {if(a[k+1]/1000>10){m+=a[k+1]/1000;print a[k],a[k+1]/1000,m}}}'

因为这些tsm文件导致的常驻内存的大小就有6.1G,这还只是筛选出大于10M的那些数据文件,总的应该会更多。那么问题来了,influxdb为什么会将这些文件引入内存?

高性能利器-mmap

mmap(memory mapped file)是一种内存映射文件的机制,它可以将文件映射到进程的地址空间中,使得文件的内容在内存中像数组一样被访问。在Linux和UNIX系统中,mmap函数允许进程把一个文件或者其他对象映射到它的进程空间,从而实现文件内容与虚拟内存之间的映射。使用mmap函数映射文件可以提高文件读取速度,减少IO开销,特别是对于大文件的读取操作。当文件被映射到进程的虚拟地址空间中时,进程可以像访问内存一样访问文件中的内容,而不需要进行系统调用读取磁盘上的数据。

我们都知道influxdb为了保证数据可靠性会将数据落盘,为了保证查询的效率足够高,他也利用了mmap技术将数据文件tsm进行了内存映射,使得influxdb可以像操作内存一样操作数据文件,避免了传统模式那种多次上下文切换带来的功能损耗,从而让influxdb性能更优。本文结合influxdb 1.8.4的源码进行此过程的梳理

a. influxdb启动时会调用文件存储层打开对于文件等的引用

b.创建文件的阅读器

c.阅读器的初始化

d.文件的mmap操作进行内存映射

mmap函数会将指定的文件区域映射到进程的地址空间中,它会在虚拟地址空间中分配一段连续的地址,并将这段地址映射到指定的文件区域中。这个操作是真正占用进程的虚拟内存的,但是在内存实际分配之前,这部分虚拟内存是不会被物理内存占用的,只有在程序访问这部分虚拟内存时,操作系统才会将其对应的物理内存分配出来,从而使得虚拟内存真正占用物理内存。因此,mmap 操作并不是直接占用内存,而是分配好虚拟内存地址。所以mmap()调用会增加进程的虚拟内存空间,因此VIRT会增大,但是并不会直接占用物理内存,因此RES不会变。实际的物理内存映射会在访问映射区域时发生,此时才会将数据载入物理内存并增加进程的实际内存使用量。那么哪些操作会将数据加入到物理内存呢?

查询数据阶段:当用户执行查询操作时,InfluxDB需要读取磁盘上的数据文件,并将其映射到虚拟地址空间中以进行数据的查询和处理。这时操作系统会将相关的内存页加载到内存中,然后将其映射到进程的虚拟地址空间中,供应用程序访问。

写入数据阶段:当用户写入数据时,InfluxDB需要将数据写入到磁盘上的数据文件中。如果数据文件已经被映射到虚拟地址空间中,那么写入数据的过程也会访问被映射的虚拟地址。写入数据时,操作系统会将修改后的内存页标记为已修改,然后在适当的时候将其写回到磁盘上的数据文件中。

压缩&合并数据阶段:当 InfluxDB 执行数据压缩操作时,它需要读取磁盘上的数据文件,并将压缩合并后的数据写入到新的数据文件中。这个过程也会访问被映射的虚拟地址,类似于查询数据阶段。压缩数据时,InfluxDB 会根据配置的策略来判断哪些数据需要被压缩,压缩后的数据会被写入新的数据文件中,并替换旧的数据文件。

回溯TOP中内存指标

基于上面的分析,我们可以对TOP中的VIRT、RES、SHR 这三个的值进行分析验证:

VIRT:虚拟内存大小,在TOP中显示的值是15G。我们通过上面的mmap分析,在influxdb启动时会进行内存映射,将数据文件映射进内存,虽然不会增加常驻内存的大小,但是会直接影响虚拟内存,而我们磁盘文件的大小是14G

这个值再加上influxdb自身的堆栈以及其他的共享库文件的占用,基本上为15G。

SHR:共享内存大小,在TOP中显示的值是6.1 G。上文我们就通过smaps拿到了映射进入内存的文件的大小是6.1G,基本上是一致的。

RES:常驻内存大小,在TOP中显示的值是6.2G。自身堆栈的占用可以通过influx工具进入查询。

自身堆栈占用大小计算:HeapInUse + (HeapIdle - HeapReleased) = 146890752+(175284224-164610048)=146890752+10674176=157564928=0.15G

RES=SHR+自身堆栈占用=6.1G+0.15G=6.15G,这个值四舍五入和6.2G是持平的。

结论

influxdb内存占用大,主要是因为利用了mmap对数据文件进行内存映射。另外为了提升查询性能,在内存中构建了大量的索引帮助快速定位数据等。

参考文档

influxdb内存消耗分析及性能优化【追踪篇】

influxdb内存消耗分析及性能优化【探索篇】

influxdb官方文档

上一篇下一篇

猜你喜欢

热点阅读