min_free_kbytes参数深入解析及优化
cat /proc/sys/vm/min_free_kbytes
min_free_kbytes这个参数,相信很多同学都听说过。给系统预留的最低内存数。但是这个说法太泛泛了,不能完全说清楚这个参数的实际意义。
内核有一整套的内存管理的系统:
- 一方面要保证性能(让用户申请内存尽可能快)
- 一方面要确保稳定(紧急时刻有内存保命)
需要在两者之间做均衡。
内存申请时,有很多种类型,有各种flag描述内存申请的策略,为了更好地描述,这里我简单就归为2类:
- 可以接受等待,内存不足时,等待内存回收,直到申请到为止。
- 不可接受等待,内存不足时,直接失败。
普通的用户态程序申请内存都是第一类,当内存不足时,可能会由于需要触发回收而导致申请过程的时间变长影响到性能。
一些内核态紧急的内存申请,常见的如网络收发包阶段,很多内存申请都是不可等待的,一旦内存不足,就报失败,表现就可能是丢包,甚至更加严重。
因此,内核会尽可能确保一部分内存是空闲的,给第二类内存申请使用。确保整个系统稳定。这部分内存就就是min_free_kbytes参数控制的。再想一下,就会有这样的疑问:如何做到将这么多的内存空出来呢?这就涉及到内存的回收策略。
仅依赖空闲内存低于这个值,没有任何的缓冲,来做内存回收肯定是很不靠谱的,很容易被突破。因此肯定需要在min上加一个缓冲,还没到min的时候就需要回收,回到了一定的内存之后,就不在回收了。这里就新增了两个数出来,一个是low(触发回收),一个是high(停止回收)。
有3条虚线,从下到上分别是min/low/high
1到2的区域,也就是达到low之前,都不会触发任何的内存回收。
2到3的区域,在low和min之间,任何的内存申请都会触发kswapd后台回收内存。知道free内存达到high停止回收。
3到4的区域,就分两类了,就是之前提到的2类:
- 部分特殊高优先级的需求,会直接占用min部分预留的内存。
- 普通的内存申请,会触发direct reclaim,也就是业务进程直接做内存回收,然后再次试一下申请。这种情况就是 direct reclaim(直接回收)(阻塞新的内存申请,这种情况称为Direct reclaim)。direct reclaim 会比较消耗时间的原因是,如果回收的是 dirty page,就会触发磁盘 IO 的操作
当前的内核,min/low/high都是通过min_free_kbytes控制的:
- min=min_free_kbytes
- low=min_free_kbytes+min_free_kbytes/4
- high=min_free_kbytes+min_free_kbytes/2
通常,我们需要调整这个参数,不外乎就是为了这2个原因:
1、稳定性问题,预留内存少,少数高优先级内存申请失败导致系统不稳定。
2、业务经常遇到direct reclaim导致内存申请慢导致业务抖动。
系统默认的min_free_kbytes一般只有几十MB。对于第一点,比较罕见。以前是5u/6u的机器上,遇到较多,一般都是申请高阶内存失败。高阶内存指的是连续的物理内存。有不同的阶数,阶数越高,连续的越大。在5u/6u的内核由于没有动态的内存碎片整理功能,导致内存碎片化更加严重,高阶内存申请更容易失败。
在7u上,这个问题得到显著的改善。
在7u上,调整min_free_kbytes更多得是降低direct reclaim的概率。现在的机器,内存越来越大,整机性能越来越好,单机跑的业务越来越多。每次空闲内存低于min值时,direct reclaim对于业务的影响也越来越明显。特别是rt敏感型的业务,比如DB,影响更是巨大。
而实际上,降低direct reclaim的概率,这个效果也不是非常好,从前面的分析可以看到,free内存低于min值之后,申请内存都会触发direct reclaim。调大这个参数,也直接让触发direct reclaim的内存水位点提高了。
这个效果很一般,主要的效果来自于每次触发kswapd直接回收时,回收的内存变多了。每次回收大约0.5*min_free_kbytes,设置的越大,回收得越多。
这个的代价也非常昂贵,min以下的内存用户态程序是完全不可用的状态。花了很高的代价,获得微小的收益,非常得划不来。且问题依旧严峻。
未来提升内存的使用率后,内存的水位会急剧升高。且单机的内存量也是持续上升,这个问题亟待解决!
要降低direct reclaim的概率,本质上是需要拉大low和min的差值,提供更多的缓冲给到kswapd,让一段时间内kswapd的回收效率跟得上内存申请的速度。单独提供一个内核参数,专门控制low值,而不涉及min值。看了一下upstream上的内核,发现,这个参数已经存在了。。。 https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable.git/commit/?id=795ae7a0de6b834a0cc202aa55c190ef81496665
这个patch在4.9的内核中已经存在。watermark_scale_factor 这个参数用来控制low-min的值。
显示内存水位线的脚本
pages_low=1.25倍pages_min,page_high=1.5倍pages_min,当pages_free < pages_low时,就会触发内存的回收,可以通过/proc/sys/vm/min_free_kbytes设置pages_min来控制kswapd0的回收时机
也可以通过下面脚本获取
mymin() {
T=min
sum=0
for i in $(cat /proc/zoneinfo | grep $T | awk '{print $NF}'); do
let sum=$sum+$i
done
#echo $sum
let min=$sum*4/1024
echo "min=${min} MB"
}
mylow() {
T=low
sum=0
for i in $(cat /proc/zoneinfo | grep $T | awk '{print $NF}'); do
let sum=$sum+$i
done
let low=$sum*4/1024
echo "low=${low} MB"
}
myhigh() {
T=high
sum=0
for i in $(cat /proc/zoneinfo | grep $T | awk '{print $NF}'); do
let sum=$sum+$i
done
let high=$sum*4/1024
echo "high=${high} MB"
}
mymin
mylow
myhigh
zoneinfo 显示的占用的页个数,一个页为4K