写该篇文章的用意不在于怎么解决某个问题,而是希望表达出因这次线上问题而引发出解决问题的思路。
问题背景
公司内部的一个license服务器,部署了一套apache+mod_wsgi+python服务,该服务用户量很小,但是内存使用量却很大,其中有一个比较奇怪的现象,就是我通过top命令看到的进程使用内存很小,但是free命令看到的使用内存确很大,下面我会先引出问题然后说一下我的解题思路
解题一
首先free命令查看使用和空闲内存,如下:
$ free -m
total used free shared buffers cached
Mem: 1877 1736 141 0 6 23
-/+ buffers/cache: 1706 170
Swap: 0 0 0
可能你已经发现内存使用达到了1736m(这不是重点),这里说一下我们的系统使用用户就只有几个人,甚至并发都没有,正常情况不可能使用1个多G的内存。
接下来我们top +M命令看一下进程用量:
top - 09:45:01 up 1 day, 23:59, 2 users, load average: 0.00, 0.00, 0.00
Tasks: 112 total, 2 running, 110 sleeping, 0 stopped, 0 zombie
Cpu(s): 1.8%us, 1.5%sy, 0.0%ni, 96.7%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Mem: 1922620k total, 1778904k used, 143716k free, 6900k buffers
Swap: 0k total, 0k used, 0k free, 23564k cached
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
2639 apache 20 0 1722m 265m 3588 S 0.0 14.1 2:20.72 httpd
6246 apache 20 0 1530m 56m 3576 S 0.0 3.0 2:40.17 httpd
2564 apache 20 0 1530m 42m 3616 S 0.3 2.3 1:35.79 httpd
1641 mysql 20 0 818m 31m 2448 S 0.0 1.7 8:54.25 mysqld
8895 apache 20 0 881m 29m 6496 S 0.0 1.6 0:25.66 httpd
27060 root 20 0 28824 3052 2240 S 0.3 0.2 0:03.14 sshd
21110 root 20 0 28716 2836 2176 S 0.0 0.1 0:00.12 sshd
27460 root 20 0 28444 2692 2176 S 0.0 0.1 0:00.04 sshd
1179 root 20 0 165m 2004 1132 S 0.0 0.1 2:30.19 vmtoolsd
27062 root 20 0 105m 1996 1536 S 0.0 0.1 0:00.08 bash
27075 root 20 0 105m 1984 1544 S 0.0 0.1 0:00.00 bash
1312 root 20 0 88788 1956 488 S 0.0 0.1 0:16.80 httpd
27468 root 20 0 27732 1776 1352 S 0.0 0.1 0:00.00 sftp-server
21133 root 20 0 27732 1708 1236 S 0.0 0.1 0:00.00 sftp-server
21127 root 20 0 27732 1684 1236 S 0.0 0.1 0:00.02 sftp-server
21114 root 20 0 27732 1636 1236 S 0.0 0.1 0:00.02 sftp-server
有心人已经发现了问题,what append? 我的内存哪里去了,通过top命令观察进程使用的内存量最多也就400多m,但是free明明使用总量1736m。
注意: 有经验的人可能会说linux系统会使用buffers/cache来提高处理性能而且这部分内存是可以释放的,很多人都知道大名鼎鼎的《Linux ate my linux》网站这里附上网址:https://www.linuxatemyram.com/,这篇网站介绍的也大都是buffers、cache占用系统内存以提高性能,但是这里请注意看我的free命令结果,buffers为6m,cache为23m,used和top进程相差还是太大了。
太奇怪了,谁吃了我的内存!!!
解题二
上面我们已经知道,既然不是buffers/cache的问题,那内存哪里去了?会不会是free命令或者top命令的问题呢,既然free命令可以统计内存那她是怎么统计的呢。top又是怎么统计每个进程的内存呢,我们先来分析top命令,其实top命令是读取的/proc/$PID/statm文件,/proc/$PID/statm文件用linux官方的描述为:Process memory status information详细信息请参考连接https://www.kernel.org/doc/html/latest/filesystems/proc.html,
/proc/$PID/statm文件内容如下:
$ cat /proc/1449/statm
2762 258 75 36 0 195 0
该文件包含了7个字段,每个字段的含义可参考下表:
Field | Content | |
size | total program size (pages) | (same as VmSize in status) |
resident | size of memory portions (pages) | (same as VmRSS in status) |
shared | number of pages that are shared | (i.e. backed by a file, same as RssFile+RssShmem in status) |
trs | number of pages that are ‘code’ | (not including libs; broken, includes data segment) |
lrs | number of pages of library | (always 0 on 2.6) |
drs | number of pages of data/stack | (including libs; broken, includes library text) |
dt | number of dirty pages | (always 0 on 2.6) |
上面的七个字段我们要特别留意其中的第二个字段也就是resident, 该字段在linux中有一个专业的术语叫RSS(Resident Set Size)翻译为中文就是:驻留集大小,其实使用过ps命令的同学应该注意到过,ps命令会输出进程的两个字段:RSS,VSZ,如下第五和第六列:
$ ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 19232 1504 ? Ss Jul28 0:01 /sbin/init
root 2 0.0 0.0 0 0 ? S Jul28 0:00 [kthreadd]
root 3 0.0 0.0 0 0 ? S Jul28 0:00 [migration/0]
上面ps命令结果中的RSS字段就是对应的statm文件的第二个字段,细心的同学可能又发现了问题,不对啊?为什么statm文件的结果和ps命令的结果不一样的,这是因为statm文件的字段单位是page,我们知道linux是使用内存页来分配和寻址的,所以这里我们应该把statm结果的resident字段乘以内存页的大小(默认为4kb,也可以使用getconf PAGESIZE命令查看)。
关于RSS和VSZ的详细介绍这里不做过多解释,可以参考我的这篇文章,里面解释的很清楚:linux ps命令VSZ和RSS内存使用的区别
这里我们姑且认为RSS也就是/proc/1449/statm 文件的第二个字段就是我们程序使用的实际物理内存(其实也差不多了),也是top命令读取进程内存的大小,看到这里我们应该可以想到那我是不是只要统计/proc目录下所有进程的RSS内存信息不就可以知道所有进程占用的内存了吗?答案是肯定的,我写了下面的脚本用来统计系统所有进程的内存和,可以用作参考:
$ cat RSS.sh
#!/bin/bash
for PROC in `ls /proc/|grep "^[0-9]"`
do
if [ -f /proc/$PROC/statm ]; then
TEP=`cat /proc/$PROC/statm | awk '{print ($2)}'`
RSS=`expr $RSS + $TEP`
fi
done
RSS=`expr $RSS \* 4` # 注意这里要乘以4(内存页大小)
echo $RSS"KB"
那么上面我们已经可以统计出来系统中进程所占用的总内存了,但是这还不够,其实我们还可以使用一个工具来统计系统所占用的内存:nmon,该工具可以很直观的统计出系统内存被什么给占用,如下:
$ nmon
上图圈起来的部分,我们可以看到还有两个东西占用了内存:Slab和PageTables,这两个又是什么东西?由于篇幅原因我这里只做一个简单的介绍,
简单的说内核为了高性能每个需要重复使用的对象都会有个池,这个slab池会cache大量常用的对象,所以会消耗大量的内存。运行命令:
$ slabtop
Active / Total Objects (% used) : 90854 / 97978 (92.7%)
Active / Total Slabs (% used) : 5073 / 5073 (100.0%)
Active / Total Caches (% used) : 97 / 181 (53.6%)
Active / Total Size (% used) : 24085.29K / 25296.83K (95.2%)
Minimum / Average / Maximum Object : 0.02K / 0.26K / 4096.00K
OBJS ACTIVE USE OBJ SIZE SLABS OBJ/SLAB CACHE SIZE NAME
18256 18124 99% 0.03K 163 112 652K size-32
8964 8950 99% 0.14K 332 27 1328K sysfs_dir_cache
8319 8010 96% 0.06K 141 59 564K size-64
8200 7510 91% 0.19K 410 20 1640K dentry
7632 7573 99% 0.07K 144 53 576K selinux_inode_security
4580 4577 99% 0.19K 229 20 916K size-192
4399 4286 97% 0.07K 83 53 332K Acpi-Operand
4275 3675 85% 0.20K 225 19 900K vm_area_struct
4116 4101 99% 0.58K 686 6 2744K inode_cache
3619 2877 79% 0.05K 47 77 188K anon_vma_chain
3312 3224 97% 0.04K 36 92 144K Acpi-Namespace
2516 1152 45% 0.10K 68 37 272K buffer_head
2212 1767 79% 0.55K 316 7 1264K radix_tree_node
1932 1669 86% 0.04K 21 92 84K anon_vma
1560 1328 85% 0.19K 78 20 312K filp
1500 1460 97% 0.12K 50 30 200K size-128
1020 992 97% 1.00K 255 4 1020K size-1024
996 887 89% 0.62K 166 6 664K proc_inode_cache
920 904 98% 0.04K 10 92 40K dm_io
864 860 99% 0.02K 6 144 24K dm_target_io
768 757 98% 0.50K 96 8 384K size-512
640 634 99% 0.78K 128 5 512K shmem_inode_cache
588 557 94% 1.00K 147 4 588K ext4_inode_cache
468 456 97% 2.00K 234 2 936K size-2048
440 375 85% 0.19K 22 20 88K cred_jar
390 281 72% 0.12K 13 30 52K pid
360 324 90% 0.25K 24 15 96K skbuff_head_cache
340 283 83% 0.11K 10 34 40K task_delay_info
261 256 98% 2.59K 87 3 696K task_struct
240 118 49% 0.08K 5 48 20K blkdev_ioc
238 233 97% 0.53K 34 7 136K idr_layer_cache
234 234 100% 4.00K 234 1 936K size-4096
225 214 95% 0.81K 25 9 200K task_xstate
从图我们可以看出各种对象的大小和数目,遗憾的是没有告诉我们slab消耗了多少内存。
我们自己来算下好了:
$ echo `cat /proc/slabinfo |awk 'BEGIN{sum=0;}{sum=sum+$3*$4;}END{print sum/1024/1024}'` MB
31.6606 MB
好吧,把每个对象的数目*大小,再累加,我们就得到了总的内存消耗量:31M
那么PageTables呢?
简单来说是linux用于记录和管理虚拟内存地址的,这是一个硬性开销,具体可自行搜索了解
好吧,知道是干嘛的啦,管理这些物理页面的硬开销,那么具体是多少呢?如下命令:
$ echo `grep PageTables /proc/meminfo | awk '{print $2}'` KB
58052 KB
通过上面的分析,我对linux系统内存的消耗做了一下总结:
- 进程消耗。
- slab消耗
3.pagetable消耗。
我们把三种消耗汇总下和free出的结果比对,我汇总成了一下脚本,用来统计三种内存消耗并和free命令做了对比:
$ cat cm.sh
#!/bin/bash
for PROC in `ls /proc/|grep "^[0-9]"`
do
if [ -f /proc/$PROC/statm ]; then
TEP=`cat /proc/$PROC/statm | awk '{print ($2)}'`
RSS=`expr $RSS + $TEP`
fi
done
RSS=`expr $RSS \* 4`
PageTable=`grep PageTables /proc/meminfo | awk '{print $2}'`
SlabInfo=`cat /proc/slabinfo |awk 'BEGIN{sum=0;}{sum=sum+$3*$4;}END{print sum/1024/1024}'`
echo $RSS"KB", $PageTable"KB", $SlabInfo"MB"
printf "rss+pagetable+slabinfo=%sMB\n" `echo $RSS/1024 + $PageTable/1024 + $SlabInfo|bc`
free -m
我们运行一下,看下结果:
$ ./cm.sh
357528KB, 4664KB, 28.5003MB
rss+pagetable+slabinfo=381.5003MB
total used free shared buffers cached
Mem: 3833 2991 841 0 20 102
-/+ buffers/cache: 2868 964
Swap: 4031 13 4018
上面的结果不需要太关注我的内存总大小,因为我后来加了内存,但是现象还是一样的现象。
free结果说使用了2868m, 我们的cm脚本中的rss+pagetable+slabinfo=381.5003MB,有没有被吓到,rss+slab+pagetable一共才用了381m,但是free统计却使用了高达2868m那我另外的内存被用在哪里了?
解题三
其实通过上面的分析我们已经可以知道cm.sh脚本统计的内存应该是没有问题了,那我们应该想到另外一个问题:那是不是free命令统计的有问题呢?free命令中的内存信息从哪里来的呢?其实free命令是读取的/proc/meminfo文件,关于/etc/meminfo文件中的字段含义可以查看这个链接:https://www.howtouselinux.com/post/linux-memory-metrics-proc-meminfo 那么我们来对比下cm.sh脚本的结果和meminfo中内存信息:
$ ./cm.sh
1367988KB, 6880KB, 27.7792MB
rss+pagetable+slabinfo=1368.7792MB
total used free shared buffers cached
Mem: 3833 2694 1138 0 1 56
-/+ buffers/cache: 2637 1195
Swap: 4031 33 3998
$ cat /proc/meminfo
MemTotal: 3925040 kB
MemFree: 1166624 kB
Buffers: 1708 kB
Cached: 57548 kB
SwapCached: 2936 kB
···
上面我截取了meminfo文件中的关键信息,其他信息省略了,通过对比我们发现rss+pagetable_slabinfo=1368m,而free命令的used为2637,且free命令取meminfo文件内存数据也没有错,既然free命令没有错,统计的rss+pagetable+slabinfo内存也没有错,那meminfo命令中的MemFree为什么这么少呢?这个时候我们就想到了是不是meminfo文件中的MemFree统计有问题?
其实这里有引出了另外一个问题,linux对内存分配的统计是怎么样的,也就是linux会把所有分配出去的内存进行统计吗?答案是否定的,其实linux kernal动态分配的内存就有一部分没有计入/proc/meminfo文件中。
以下部分截取自另一篇文章:
我们知道,Kernel的动态内存分配通过以下几种接口:
alloc_pages/__get_free_page: 以页为单位分配
vmalloc: 以字节为单位分配虚拟地址连续的内存块
slab allocator
kmalloc: 以字节为单位分配物理地址连续的内存块,它是以slab为基础的,使用slab层的general caches — 大小为2^n,名称是kmalloc-32、kmalloc-64等(在老kernel上的名称是size-32、size-64等)。
通过slab层分配的内存会被精确统计,可以参见/proc/meminfo中的slab/SReclaimable/SUnreclaim;
通过vmalloc分配的内存也有统计,参见/proc/meminfo中的VmallocUsed 和 /proc/vmallocinfo(下节中还有详述);
而通过alloc_pages分配的内存不会自动统计,除非调用alloc_pages的内核模块或驱动程序主动进行统计,否则我们只能看到free memory减少了,但从/proc/meminfo中看不出它们具体用到哪里去了。比如在VMware guest上有一个常见问题,就是VMWare ESX宿主机会通过guest上的Balloon driver(vmware_balloon module)占用guest的内存,有时占用得太多会导致guest无内存可用,这时去检查guest的/proc/meminfo只看见MemFree很少、但看不出内存的去向,原因就是Balloon driver通过alloc_pages分配内存,没有在/proc/meminfo中留下统计值,所以很难追踪。
总结
通过上面的分析,我们知道MemFree不知去向的一个原因就是Balloon driver通过alloc_pages抢走了内存,然而使用balloon driver的最大可能就是vmware esx服务,后来联系了公司的私有云维护人员协助调研发现,公司确实使用了vmware esx服务,而且其中一台host内存已经接近瓶颈,把我们使用的机器切到了别的host上问题解决🤦♂️🤦♂️🤦♂️,是不是感觉整个人都傻了。这里提供两个文章可以供大家了解一下vmware esx服务的内存共享机制:https://blog.51cto.com/qq694157416/1345680
最终的结果很让人意外,但本篇文章希望提供一个内存分析的解题思路,授人以鱼不如授人以渔!!!