Skip to main content

Command Palette

Search for a command to run...

内存问题的排查

Updated
4 min read

本周排查线上一次抖动,部署的mongo时不时会出现请求latency变高的问题;leader让我排查一下为什么会抖动的;因为是海外机房,其实我基本上已经确认是内存的问题的, 因为海外资源比较有限,很多进程都会部署在一个机器上的,相互影响比较严重的;当然基本确认是什么问题,但是还是需要去验证一下;

1. 排查的方式

内存问题影响到业务层基本上的原因在于:

当出现内存极端不足的情况的时候,用户进程如果这个时候申请内存的时候,操作系统会因为没有足够的物理内存,从而触发操作系统的直接回收内存的模型,这个内存回收会在用户申请内存的上下文中直接触发的,也就是本来你去申请内存,这个时候却在做回收内存的;从而导致申请内存操作latency变高,从而引起了正常的业务请求抖动的;

通常要尽力避免进入这样的内存回收状态,因为这个时候在机器的所有的进程都是会被影响的,从而导致不可控的抖动的;那有哪几种的方式可以观察到这种情况的:

  • tsar
tsar --mem -i 1 -l
Time              -----------------------mem---------------------- ---------------swap-------------
Time                free    used    buff    cach   total    util    swpin  swpout   total    util
03/04/23-21:09:48   4.3G   84.0G    1.1G  162.4G  251.8G   33.35     0.00    0.00    0.00
03/04/23-21:09:49   4.2G   84.0G    1.1G  162.5G  251.8G   33.36     0.00    0.00    0.00
03/04/23-21:09:50   4.1G   84.0G    1.1G  162.6G  251.8G   33.35     0.00    0.00    0.00
03/04/23-21:09:51   4.0G   84.0G    1.1G  162.7G  251.8G   33.36     0.00    0.00    0.00

* mem.used: 已用内存大小(单位:KB)。
* mem.buff: Buffers占用的内存大小(单位:KB)。
* mem.cach: Cache占用的内存大小(单位:KB)。
* mem.free: 空闲内存大小(单位:KB)。
* mem.total: 总共的物理内存
* mem.util: 内存使用的百分比

* swap.swpin: 磁盘交换到内存的大小
* swap.swpout: 内存交换到磁盘的大小
* swap.total: 可用的swap的总共大小
* swap.util: 使用百分比

通常关注`free`和`used`两个指标,内存不足的时候你可能关注到free指标不断下降,时不时就还会慢慢涨回来
swap: 表示内存和磁盘之间的交换,通常线上机器都会将这个关闭,原因是磁盘的速度太慢,如果开启会在交换时候非常缓慢,并且对磁盘的压力也很大

tsar本身能看到的内存信息会比较少,尤其是direct reclaim这个指标不是很明确

  • sar
sar -rRB 1 100000
-r: 内存的统计信息
-R: 缓存的统计信息
-B: 页面缓存的统计信息
Linux 4.18.5-041805-generic (jjh2972)     04/03/2023     _x86_64_    (48 CPU)

09:21:27 PM  pgpgin/s pgpgout/s   fault/s  majflt/s  pgfree/s pgscank/s pgscand/s pgsteal/s    %vmeff
09:21:28 PM      0.00  87068.00  33798.00      0.00  32141.00      0.00      0.00      0.00      0.00

09:21:27 PM   frmpg/s   bufpg/s   campg/s
09:21:28 PM  -6708.00      0.00   6689.00

09:21:27 PM kbmemfree kbmemused  %memused kbbuffers  kbcached  kbcommit   %commit  kbactive   kbinact   kbdirty
09:21:28 PM   6127096 257892216     97.68   1120412 168752100 113597140     43.03 181323284  71838856     36776

指标解释:

* kbmemfree:空闲内存大小,单位是KB。
* kbmemused:使用中的内存大小,单位是KB。
* %memused:使用中的内存占比。
* kbbuffers:系统buffer的大小,单位是KB。
* kbcached:系统cache的大小,单位是KB。
* kbcommit:已经分配但未使用的内存大小,单位是KB。
* %commit:已经分配但未使用的内存占比。
* kbactive:正在使用的活动内存大小,单位是KB。
* kbinact:不活跃的内存大小,单位是KB。
* kbdirty:等待刷新到磁盘的内存大小,单位是KB。

* frmpg/s: 每秒被free的page的个数;如果是负值就表示有page被分配
* bufpg/s: 每秒被分配用于buffer的page的个数,如果是负值就表示很少有page被用于buffer
* campg/s: 每秒被分配用于cache的page的个数,如果是负值,表示几乎没有page用在cache中

* pgpgin/s: 每秒从磁盘进来的page的总kb
* pgpgout/s: 每秒从系统内存回退到磁盘的总kb
* fault/s: 缺页的次数,这个包含了major+minor,这个次数不是全部度会产生IO的; 有的只是虚拟地址与物理地址没有对应,缺页的解决方法只需要对应一下就好; 而有的则需要从磁盘中load上来;
* magflt/s: 会引起io操作的缺页处理
* pgfree/s: 每秒被放到free list的page的个数
* pgscank/s: kswapd每秒scan的page的个数
* pgscand/s: 直接回收每秒scan的page的个数
* pgsteal/s: 每秒被回收的page的个数
* vmeff: 计算方式(pgsteal/(pgscank+pgscand)), 这是反映内存回收的效率; 如果这个值很高就表示在inactive尾部的一些page都将被回收; 如果太小,就表示很难有page可以被回收回来

主要关注的指标有:
* kbmemfree: 表示当前空闲内存的大小,可以大概看一下,是不是空闲内存很不足; 
* kbdirty: 如果这个数值很大的话,就说明有大量的内存需要被回写到磁盘,也可能会造成一次回写过多导致磁盘压力很大的;可以通过内核参数进行配置;
* magflt/s: 表示重大缺页的次数,这个频率很高的话就表示内存不足的表现,因为需要不断的剔除一些old page,存放new page,这个过程对cpu来说很慢
* pgscand/s:表示应用进程申请内存,因为操作系统内存不足导致需要在当前context执行遍历page来释放内存的过程的;这个指标非常的重要的,如果这个指标频繁出现,那么就表示当前系统的可用内存严重不足,已经开始影响到了应用进程了;
* vmeff: 这是回收效率的体现,看计算方式可以看出: `真是回收/扫描的page的总数`,内存充足的情况下,通常为0,需要配合`pgscank pgscand`来一起使用;
  • 连续内存不足的问题

有的时候,你会发现free内存比较多,但是pgscand/s依然频频出现,这个时候就需要思考是否是连续内存不足的问题;因为当用户申请连续内存的时候,虽然有虚拟内存地址,其实并不限制物理内存需要连续的,但是操作系统倾向于物理内存也是连续的,估计是性能上会更加好一些吧的;所以如果连续内存不足,其实也会导致操作系统进入类似模型进行内存回收,影响应用进程的;

cat /proc/buddyinfo

Node 0, zone      DMA      2      3      2      2      2      2      2      1      1      2      2
Node 0, zone    DMA32  12631  11776   4183   1428    118     40     17      8      3      5      0
Node 0, zone   Normal 580413      0      4      7      4      3      2      0      0      1      0
Node 1, zone   Normal 585174 170141  61788  18068   2506    533    124     32     12      0      0

只需要关注Normal两行即可:
* node: numa的概念, 通常在numa架构cpu申请内存会优先从自己节点的内存获得
* 后面的一列一列数据表示: `2^0`, `2^1`, `2^2`...连续n个page的空余数;比如`2^2`就是表示有连续4个page的内存的个数;

buddyinfo中输出的信息中有Node的概念,这是numa里面的概念,随着服务器的cpu的核数越来越多,在硬件层面上core和内存之间就会存在远近的问题,比较正规的叫法: NUMA节点,每个 NUMA 节点都有自己的 CPU、内存和 I/O 等资源,是一个独立的计算资源集,而在不同节点的cpu访问local内存和remote内存的时候延迟是不一样的,几乎是double的差距;而上面的buddyinfo中的信息输出就表示当前系统有2个NUMA节点,当前Node 0的连续page的个数比较少;可能会存在内存不足的情况;

当然为什么是可能呢? 因为很多系统本质上是不开启numa的,不开启numa的话,本质上不同节点的内存对操作系统来说是统一调配的,所以如果Node 0不够就会从Node 1进行分配;

numactl --hardware

输出结果:
available: 2 nodes (0-1)
node 0 cpus: 0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30
node 0 size: 96456 MB
node 0 free: 2724 MB
node 1 cpus: 1 3 5 7 9 11 13 15 17 19 21 23 25 27 29 31
node 1 size: 96757 MB
node 1 free: 2520 MB
node distances:
node   0   1
  0:  10  21
  1:  21  10

这些信息表示:
1. 当前系统存在两个numa节点
2. node x size: 表示当前节点下面的总内存 ( cat /sys/devices/system/node/nodex/meminfo 也可以观察)
3. node x cpu: 哪些cpu是属于哪个node,这个比较重要,因为绑核操作的话,需要关注这些物理限制,尽可能将进程绑在相同的node下面的cpu
4. node x free: 表示当前node剩余的内存
5. node distances: 大致表示节点之间的距离,也表示相互调用之间的性能差距,这是一个相对比较的;比如上面的这个信息输出,基本可以认为调用本地内存和remote内存的latency基本在2倍左右,当然具体还是要根据实测为主;

从上面的输出结果可以看到两个节点的free基本上差不多的;主要原因是因为启动程序的时候指定了numactl --interleave=all这个参数,这个参数会强制程序在多Node之间做内存的负载均衡,保证两个节点的内存使用率近乎相同;所以整体就非常均衡的;

偶尔需要关注一下buddyinfo连续内存是否足够,如果不够的情况下,也是会触发direct reclaim这个模型,影响到用户进程;

  • 其他

还有哪些情况需要关注呢?

  1. swap分区;正常情况下我们在服务器上会关闭swap分区,原因是磁盘的性能是跟不上内存的,如果有了swap分区就表示系统认为的内存大小要大于真实物理内存,而中间的取舍就是性能;遇到一次申请很大的内存的时候,系统为了获得足够的内存就会将内存写入到磁盘来获得更加多的空余内存,或者是从磁盘中加载之前换出的内存数据,这两个过程会很慢,触发很严重的抖动问题;通过上面的tsar可以看到对应的swap分区的使用率,优先关闭swap;

  2. dirty page: 操作系统本身自己对io有一层缓冲,叫做pagecache, 大部分情况下,写文件基本都是在写内存,好处是速度快,问题是安全性比较差,如果突然关机就可能导致数据丢失;大部分的操作系统依然还是有pagecache,不然直接写磁盘设备性能会很差,那些被改变的数据的page就叫做dirty page, 说明与磁盘上的数据不一样,需要进行回写;策略基本上几个:

    • dirty ratio: 脏页比例
    • 定时: 按照固定频率来进行回写

      基本上会两者都包含的;Linux控制dirty page回写的参数有一些:

      # dirty_background_bytes 表示当前系统所能容忍的最大的脏页的字节数,如果超过就会回写磁盘,0表示禁用
      vm.dirty_background_bytes = 0 
      # dirty page所占用比例,如果超过这个比例就进行回收
      vm.dirty_background_ratio = 10
      # 与dirty_background_bytes 不同的在于,当超过配置之后会停止用户进程,尽可能的将dirty写回到磁盘
      vm.dirty_bytes = 0
      # 每个dirty page在内存中保留的最长时间,单位为ms
      vm.dirty_expire_centisecs = 3000
      # 同dirty_background_ratio,会影响用户进程
      vm.dirty_ratio = 20
      # 表示回写dirty page到磁盘的最小时间间隔,定时回写(ms)
      vm.dirty_writeback_centisecs = 500
      
      /proc/vmstat中有一些数据:
      # 单位为page,触发直接dirty回写的阈值
      nr_dirty_threshold 4527190
      nr_dirty_background_threshold 2263595
      

      目前没找到对应比较好的指标能看到direct page writeback,只能从几个方面联合看看吧;

2. 如何缓解内存问题

为什么算是缓解而不是彻底解决呢?因为本质上是一种内存资源不够的问题导致的,虚拟内存机制保证了每一个进程都认为自己有一个很大的内存空间,但是真实的物理内存是那么大,除了所有用户进程在共用还有操作系统本身也公用了,所以在真实物理内存有限的情况下,而内存需求方瞬间需要大量的内存的时候就会遇到各种问题的。虽然不能彻底解决这个问题,但是可以通过一些机制来提早将一些无用的内存进行回收留作后用,而这就是我们的解决方式。

Linux的内存管理挺复杂的,只能具体问题具体分析的;这次我们要解决的是减少内存的direct reclaim的频率,原因是进入这个状态之后会极大的影响用户进程的,用户进程会直接参与去回收内存,而这个过程就会增加请求的latency; 类似于:

本来我去外面的餐馆吃饭,但是由于现在米不够了,不能分配足够的饭给我;于是饭店就要求我去农田里帮他们割稻生产出米,直到足够满足我一开始的要求的饭为止(不确定是不是真的是这样的);你可以思考一下用户进行用malloc申请内存之后就进入了这种状态,整个耗时还能控制吗?

2.1 内存回收的基本模式

Linux的内存回收模式大概分为两种:

  • direct reclaim: 这是一个同步回收内存的过程;当用户进程申请内存的时候,操作系统发现系统空余内存低于一个程度之后就会进入这个状态;这个状态极端影响性能
  • background 内存回收: Linux中有一个内核线程叫做kswapd,这个线程按照一定的频率会工作,主要的工作是判断当前的空余内存是否低于一定的水平,如果低于的话就启动回收内存,将一些不活跃的page换出或者释放,来得到更多的空余内存,当空余内存高于某一个水平的时候,kswapd就会停止这个回收过程的;因为这个过程是异步的,所以对用户进程来说是没有感知的;

为什么要区分两个工作模式的?原因也很简单的,当kswapd已经无能为力的时候,就要direct reclaim介入,其实kswapd和申请内存的进程之前是一个生产者和消费者的过程,当生产者跟不上消费者的时候,消费者就只能等待的;与其等待还不如自力更生(direct reclaim)去生产呢;所以如果只有kswapd的话那么当内存不够的时候,整个系统也是被hang住的,因为这个时候内存就是不足;

既然知道了在不同的内存水平线的时候会有不一样的行为,那么这个水平线是如何确定的呢?

// 包函了不同numa node的当前内存的一些信息, 我们重点看Node 0和Node 1的Normal的信息,
cat /proc/zoneinfo

Node 0, zone   Normal
  pages free     670703
        min      385498
        low      481872
        high     578247

Node 1, zone   Normal
  pages free     759799
        min      393829
        low      492286
        high     590743

其中存在有min|low|high这几个指标,这几个指标就是控制内存的行为;

image-20230409160449378

  1. 当内存低于Low的时候,Kswapd就会开始工作
  2. 当内存高于High的时候,Kswapd就会停止工作
  3. 当内存低于Min的时候,那么就进入direct reclaim

关于min|low|high是如何计算的,linux里面一段代码来解释: 主要的过程是:

  1. 根据vm.min_free_kbytes这个参数获得pages_min
  2. 按照zone的比例进行划分最小的min
  3. low = min + 1/4 * min
  4. high = min + 1/2 * min

当然是不是一定是这样的,会因为min_free_kbytes设置的value不一样而不一样,过小的时候会有其他的参数影响;

void __setup_per_zone_wmarks(void)
{
    unsigned long pages_min = min_free_kbytes >> (PAGE_SHIFT - 10);

    for_each_zone(zone) {
        tmp = (u64)pages_min * zone->managed_pages;
    do_div(tmp, lowmem_pages);

        zone->watermark[WMARK_MIN] = tmp;
        zone->watermark[WMARK_LOW]  = min_wmark_pages(zone) + (tmp >> 2);
        zone->watermark[WMARK_HIGH] = min_wmark_pages(zone) + (tmp >> 1);
    ...
}

所以要修改内存的行为的话,主要是修改vm.min_free_kbytes即可;通过提升这个数值,来提前让kswapd启动来进行回收内存,也可以增加minlow之间的差距;

2.2 经验
# vim /etc/sysctl.conf 增加以下行,参数值根据上面计算所得

内核版本差异  配置有所区分
# 4.6之前
vm.min_free_kbytes=1048576 # 1G每64G

# 4.6之后
vm.min_free_kbytes=524228 # 0.5G每64G
# 这个参数也会影响low和high的value,但是不影响min
vm.watermark_scale_factor=100

# sysctl -p  使参数生效

# sysctl -a | grep vm.min_free_kbytes 查看当前配置值
3.3 其他

之前聊到过连续内存问题,操作系统也会有对应的策略:kcompactd内核线程,也是会定期去扫描node对应的page,然后进行对应的page合并,从而得到连续的大内存;

# 可以看到一些compact一些运行参数
cat /proc/vmstat |grep compact

因为现在我还没遇到对应的问题,所以不是很清楚出现了这种连续内存不足的情况下,系统会有怎么样的反应,理论上compact应该是会影响性能的,但是目前还没有direct reclaim那么明显指标来看;后面多关注一下;

4. 总结

目前能理解的内存问题基本就是这样,基本都是内存不足引发的direct reclaim从而引发的性能问题的; 能调整的参数就上面说的vm.min_free_kbytes, 当然也可以关注一下dirty page的内核参数,通过频繁的刷盘也削峰填谷;其他的大概还有NUMA node的内存分配参数的zone_reclaim_mode这个,这个参数主要是当node本地的内存不够的时候优先选择本地回收内存还是从remote的node获得内存;

这文章写了一周多,中间靠google + chatgpt一起合作来完成的,因为chatgpt有的回答一半对一半错,真的很难搞的;现在我对未知领域学习的时候对chatgpt还是要保持怀疑的态度。

More from this blog

Ai时代的工具链

本周是black Friday,我订阅了几个AI服务,还是蛮贵的...不过这样基本上构成我目前整体的知识阅读的过程,随着Ai的不断发展,工具链的替换可能是很重要的一个过程的。我主要订购了以下几个工具: Memo: 这个工具的主要作用是将视频/audio转srt,并且带有ai翻译的工具;当然我觉得它做的非常好的是,它把整个链路做的非常好的,并且可以用本地的资源做audio->text;而且它自带了很多的ai功能,比如对字幕进行进一步的AI的处理,提问,summarize和思维导图等等;目前我主要...

Nov 30, 20251 min read

做了一个噩梦

今天凌晨4点多起来看了一眼丈母娘的发烧是否ok...就导致我有点睡不着的,刷了一会推特之后又开始睡觉了,于是就开始做了一个很可怕的梦。 噩梦 那天,我不知道是在哪里..我带着女儿和我弟出去玩的,貌似是一个风景山区。于是我就带着女儿和弟弟出去玩的;我们走啊走, 沿着一条路一直走..突然看到一个小道有一家饭店的,这个饭店是比较特殊,有很多海鲜的;我看上了一只大龙虾,我问多少钱的,他说大概就70rmb就可以的。。。我觉得很划算的,我心想:我买下来,到时候把老婆叫过来一起吃的,并且告诉她这个才70rmb...

Nov 24, 20251 min read

子女教育-2

下面我分享一个推特上的一个关于子女教育的推 哈哈哈哈,李诞这个视频我看过 我给你分享几个我和我女儿之间的小故事 第一个故事 我经常给小朋友说:你们现在上学的成绩不重要,你们现在数学考试都是语文脑筋急转弯,语文考试都是历史背诵,一点用都没有,你出了社会就知道,社会根本没有选择题,社会要有选择题就好了,最难的是你遇到困难,你连门都找不到。我第一次这样讲的时候是女儿小学4年级,那时候我女儿听的一愣一愣的,她不明白,但是觉得我的理论和学校的不一样,很狂妄,但是她很喜欢,哈哈哈哈。 她什么时候真正明...

Nov 13, 20251 min read

被诈骗-马来西亚

最近我在国内,我老婆在马来;最近在计划搬家的,找的那个房子不包含一些必要的家具,于是我老婆就必须要买点家具的,主要是沙发和餐桌..我们本来计划是说去ikea去买,但是我老婆觉得ikea的家具不便宜,并且款式一般的,最终问了中介找了一个二手平台找找看不错的家具。 我老婆挑了两个家具的,我看了一下价格也不算便宜的,但是我老婆喜欢的,于是我就说你觉得ok那就购买吧。我还顺便问了一下,这个家具能不能线下看一下货的,但是我老婆说这货在很远的地方的,大概是300公里的一个城市的。那我就说这个包邮吗,我老婆说...

Nov 13, 20251 min read

当下和最近想做的事情

1. Current 当下 最近依然还在中国,已经回来快一个月了. 最近一直在忙着带丈母娘看病和住院的。索性一切都还在可控范围内的,丈母娘由于糖尿病控制的很差导致本身的冠心病也复发. 这次去浙江省人民医院去做了造影检查和支架植入的手术的,不过这一切都比我预估的要顺利,我就怕她由于长时间没吃药和高血糖的持续的时间太长了,会带来严重的问题,不过好在没有发生最坏的事情的。 因为做了手术,所以这段时间我和我老婆的姐姐每人轮换的陪床,不过陪床真的好累的,因为睡得很不好的,特别的累。不过好在都结束了,而且丈...

Nov 9, 20251 min read

Keep Move - 永不止步

39 posts