最近公司的服务器经常被监控到内存一点点的被“蚕食”,眼看马上就要被耗尽了,这个问题要排上日程分析一下:

首先一说查看内存,我们马上就会想到使用free命令,free 命令显示系统内存的使用情况,包括物理内存、交换内存(swap)和内核缓冲区内存。使用free -h/-m命令则可以更直观看的看出内存使用情况,实际内存我们其实只需要看-/+ buffers/cache 这一行的free数据就可以。服务器初始内存为48G,我这边发现了有28G内存已经被消耗。因为我知道PHP脚本对内存方面比较不友好,当高并发的时候往往会造成内存泄漏,所以我这边在排除PHP问题的情况下继续排查问题。

进一步查找问题,我们可以使用top命令去查看各个应用的关系,在top状态下键入M可以查看由内存从大到小排序下的进程列表。果不其然,php-fpm确实没有占用大内存。从top里面我个人也没有看出来有什么异常情况。

再进一步查找问题,我们可以查看meminfo文件下对读出的内核信息进行解析,命令:cat /proc/meminfo,详情解释如下:


上面的图示我是搜了网上的一张图片,自己的信息没有上传,但是我在这边的文件信息发现了一个比较奇怪的问题:Slab栏中我的内存占用量达到了27G,这好像和我凭空消失的28G接近了,我们再去深入了解一下Slab到底是个什么东西:slab是linux操作系统的一种内存分配机制。其工作是针对一些经常分配并且释放的对象,如进程描述符等,这些对象的大小一般比较小,如果直接采用伙伴系统来进行分配和释放,不仅会造成大量的碎片,而且处理速度也会变慢。而slab分配器是基于对象进行管理的,相同类型的对象归为一类,每当要申请这样一个对象,slab分配器就从slab列表中分配一个这样大小的单元出去,而当要释放时,将其重新保存到该列表中,而不是直接返回给伙伴系统,从而避免这些内部碎片,slab分配器并不丢弃已经分配的对象,而是释放并把他们保存在内存当中,当以后又要请求新的对象时,就可以直接从内存当中获取而免于重复初始化。

说白了slab就是将内核中经常使用的对象放到高速缓存,即内存当中,避免重复初始化,系统保持高可利用状态。

当然再深奥的我也没有搞明白,但是只是知道了定义大体就搞得清楚了,我这边是由于slab生成了大量的高速缓存而没有被得到释放所以大量的内存被占用出现了现在的情况。我们再来看meminfo的slab下面两个参数:SReclaimable,SUnreclaim,分别是可回收的slab大小和不可回收的slab大小。那么我们可以通过操作来释放掉的内存大小应该就是SReclaimable的大小。就我这边的值大约是26G左右。那么问题已经比较明朗了,只需要把SReclaimable的内存清除掉就可以了。

这里我们再深入的了解一下slab,执行slabtop命令(该命令只能通过root执行),我们会看到类似top状态的一个表格,叫slab缓冲区的细节信息,这里我可以看到动态列表里面SIZE最大的一行是dentry_cache(目录项高速缓存,是linux为了提高目录项对象的处理效率而设计的,它记录了目录项到inode的映射关系),dentry_cache的大小能到26G左右。

那么查阅了dentry_cache的相关信息发现,操作系统会在到了内存临界阀值的时候进行回收释放,阀值计算为:

(1)grep low /proc/zoneinfo 得到结果

(2)将展示出得列表low后的数字相加起来*4KB,结果就是服务器的回收阀值。

我的服务器阀值算出来之后是65M,如果等到还剩65M的时候再触发释放,我的服务器在就挂掉了,所以就要修改临界阀值数。

将vm.extra_free_kbytes设置为vm.min_free_kbytes和一样大,则/proc/zoneinfo中对应的low阈值就会增大一倍,同时high阈值也会随之增长,以此类推

$ sysctl -a | grep free_kbytes
vm.min_free_kbytes = 39847
vm.extra_free_kbytes = 0
$ sysctl -w vm.extra_free_kbytes=836787 ######836787kb

当我们设置完之后,如果目前剩余内存超过了阀值,那么等待一会儿后SReclaimable的数据就会被释放掉了。