基础:为什么系统的swap变高了

本文详细介绍了Linux系统在内存资源紧张时如何通过内存回收和Swap机制来应对,包括直接内存回收、kswapd0进程的作用、内存阈值设定、NUMA架构下的内存管理,以及swappiness参数对回收策略的影响。此外,还展示了如何通过sar、/proc/zoneinfo和/proc/pid/status等工具分析Swap升高的原因,并给出了降低Swap使用的策略。
摘要由CSDN通过智能技术生成

引入

问题:当发生了内存泄露,或者运行了大内存的应用程序,导致系统的内存资源紧张时,系统会怎么应对呢?

  • OOM杀死进程
  • 内存回收

问题:什么是OOM

OOM指的是系统杀死占用大量内存的进程,释放这些内存,然后再分配给其他更需要的进程。

问题,什么是内存回收。

内存回收,指的是系统释放掉可以回收的内存,比如缓存和缓冲区,就属于可回收內存。它们在内存管理中,通常被叫做文件页

  • 大部分文件页,都可以直接回收,以后有需要时,再从磁盘重新读取就可以了
  • 而那些被应用程序修改过,并且暂时还没有写入磁盘的数据(也就是脏页),就必须先写入磁盘,然后才能进行内存释放

这些脏页,一般可以通过两种方式写入磁盘:

  • 可以在应用程序中,通过系统调用fsync,把脏页同步到磁盘中
  • 也可以交给系统,由内核线程pdfflush负责这些脏页的刷新

除了缓存和缓冲区,通过内存映射获取的文件映射页,也是一种常见的文件页。它也可以被释放掉,下次再访问的时候,从文件重新读取

除了文件页外,还有没有其他的内存可以回收呢?

比如,应用程序动态分配的堆内存,也就是我们在内存管理中说到的匿名内存,是不是也可以被回收呢?

  • 答案是这些内存不能被直接释放(回收),因为它们很可能还需要再次被访问。

  • 但是,如果这些内存在分配之后很少被访问,似乎也是一种资源浪费。是不是可以把它们暂时先存在磁盘里,释放内存给其他更需要的进程

其实,这正是Linux的swa机制,swap把这些不常访问的内存先写到磁盘里,然后释放这些内存,给其他更需要的进程使用。再次访问这些内存时,重新从磁盘读入内存就可以了

swap原理

swap原理

swap说白了就是把一块磁盘空间或者一个本地文件(下面以磁盘为例),当成内存来使用。它包括换出和换入两个过程:

  • 换出,就是把进程暂时不用的内存年数据存储到磁盘中,并释放这些数据占用的内存
  • 换入,就是在进程再次访问这些内存的时候,把它们从磁盘读到内存中来。

即,swap其实是把系统的可用内存变大了。这样,即使服务器的内存不足,也可以运行大内存的应用程序。

下面是一些swap的应用场景:

  • 即使内存不足,有些应用程序也并不想被OOM杀死,而是希望能够缓一段时间,等待人工介入,或者等系统自动释放其他进程的内存,再分配给它
  • 电脑的休眠和快速开机也是基于swap的:休眠时,把系统的内存存入磁盘,这样等到再次开机时,只需要从磁盘中加载内存就可以。这样就省去了很多应用程序的初始化过程,加快了开机速度。

问题:既然swap是为了回收内存,那么Linux到底在什么时候需要回收内存呢?前面一直说内存资源紧张,又该怎么来衡量内存是不是紧张呢?

一个最容易想到的场景就是,有新的大块内存分配请求,但是剩余内存不足。这个时候系统就需要回收一部分内存(比起前面提到的缓存),进而尽可能的满足新内存请求。这个过程通常被称为直接内存回收

除了直接内存回收,还有一个专门的内核进程用来定期的回收内存,也就是kswapd0

为了衡量内存的使⽤情况,kswapd0定义了三个内存阈值(watermark,也称为⽔位),分别是

  • ⻚最⼩阈值(pages_min)
  • ⻚低阈值(pages_low)
  • ⻚⾼阈值(pages_high)

剩余内存,则使⽤ pages_free 表示。

在这里插入图片描述
kswapd0定期扫描内存的使⽤情况,并根据剩余内存落在这三个阈值的空间位置,进⾏内存的回收操作。

  • 剩余内存⼩于⻚最⼩阈值,说明进程可⽤内存都耗尽了,只有内核才可以分配内存。
  • 剩余内存落在⻚最⼩阈值和⻚低阈值中间,说明内存压⼒⽐较⼤,剩余内存不多了。这时kswapd0会执⾏内存回收,直到剩余内存⼤于⾼阈值为⽌。
  • 剩余内存落在⻚低阈值和⻚⾼阈值中间,说明内存有⼀定压⼒,但还可以满⾜新内存请求。
  • 剩余内存⼤于⻚⾼阈值,说明剩余内存⽐较多,没有内存压⼒。

我们可以看到,⼀旦剩余内存⼩于⻚低阈值,就会触发内存的回收。这个⻚低阈值,其实可以通过内核选项/proc/sys/vm/min_free_kbytes 来间接设置。min_free_kbytes 设置了⻚最⼩阈值,⽽其他两个阈值,都是根据⻚最⼩阈值计算⽣成的,计算⽅法如下 :

pages_low = pages_min*5/4
pages_high = pages_min*3/2

NUME和Swap

很多情况下,你明明发现了swap升高,可是在分析系统的内存使用时,却很可能发现,系统剩余内存还多着呢?为什么剩余内存很多的情况下,也会发生swap呢?

是处理器的的 NUMA (Non-Uniform Memory Access)架构导致的。

  • 在NUMA架构下,多个处理器被划分到不同Node上,而且每个Node都有自己的本地内存空间
  • 而同一个Node内部的内存空间,实际上又可以进一步分为不同的内存域(Zone),比如直接内存访问区DMA、普通内存区NORMAL、伪内存区(MOVABLE)等,如下图所示:
    在这里插入图片描述

先不⽤特别关注这些内存域的具体含义,我们只要会查看阈值的配置,以及缓存、匿名⻚的
实际使⽤情况就够了。

既然 NUMA 架构下的每个 Node 都有⾃⼰的本地内存空间,那么,在分析内存的使⽤时,我们也应该针对每个 Node 单独分析。

可以通过 numactl 命令,来查看处理器在 Node 的分布情况,以及每个 Node 的内存使⽤情况。⽐如,下⾯就是⼀个numactl 输出的示例:

$ numactl --hardware
available: 1 nodes (0)
node 0 cpus: 0 1
node 0 size: 7977 MB
node 0 free: 4416 MB
...

这个界⾯显示,我的系统中只有⼀个 Node,也就是Node 0 ,⽽且编号为 0 和 1 的两个 CPU, 都位于 Node 0 上。另外,Node 0 的内存⼤⼩为 7977 MB,剩余内存为 4416 MB。

NUNA架构与NUNA内存,跟swap又有什么关系?

实际上,前⾯提到的三个内存阈值(⻚最⼩阈值、⻚低阈值和⻚⾼阈值),都可以通过内存域在 proc ⽂件系统中的接⼝/proc/zoneinfo 来查看。

⽐如,下⾯就是⼀个 /proc/zoneinfo ⽂件的内容示例:

$ cat /proc/zoneinfo
...
Node 0, zone Normal
pages free 227894
min 14896
low 18620
high 22344
...
nr_free_pages 227894
nr_zone_inactive_anon 11082
nr_zone_active_anon 14024
nr_zone_inactive_file 539024
nr_zone_active_file 923986
...

这个输出中有⼤量指标,⽐较重要的如下:

  • pages处的min、low、high,就是上⾯提到的三个内存阈值,⽽free是剩余内存⻚数,它跟后⾯的 nr_free_pages 相同。
  • nr_zone_active_anon和nr_zone_inactive_anon,分别是活跃和⾮活跃的匿名⻚数
  • nr_zone_active_file和nr_zone_inactive_file,分别是活跃和⾮活跃的⽂件⻚数。

从这个输出结果可以发现,剩余内存远⼤于⻚⾼阈值,所以此时的 kswapd0 不会回收内存。

当然,某个 Node 内存不⾜时,系统可以从其他 Node 寻找空闲内存,也可以从本地内存中回收内存。具体选哪种模式,你可以通过 /proc/sys/vm/zone_reclaim_mode 来调整。它⽀持以下⼏个选项:

  • 默认的 0 ,也就是刚刚提到的模式,表示既可以从其他 Node 寻找空闲内存,也可以从本地回收内存。
  • 1、2、4 都表示只回收本地内存,2 表示可以回写脏数据回收内存,4 表示可以⽤ Swap ⽅式回收内存

swappiness

到这里,我们就可以理解内存回收的机制了。这些回收的内存既包括了文件页,又包括了匿名页:

  • 对文件页的回收,当然是直接回收缓存,或者把脏页写回磁盘后再回收
  • 对匿名页的回收,其实就是通过swap机制,它他们写入磁盘后再释放内存

既然有两种不同的内存回收机制,那么在实际回收内存时,到底该先回收哪⼀种呢?

Linux提供了⼀个 /proc/sys/vm/swappiness 选项,⽤来调整使⽤Swap的积极程度。

  • swappiness的范围是0-100,数值越⼤,越积极使⽤Swap,也就是更倾向于回收匿名⻚;数值越⼩,越消极使⽤Swap,也就是更倾向于回收⽂件⻚。
  • 虽然 swappiness 的范围是 0-100,不过要注意,这并不是内存的百分⽐,⽽是调整 Swap 积极程度的权重,即使你把它设置成0,当剩余内存+⽂件⻚⼩于⻚⾼阈值时,还是会发⽣Swap。
  • 如果linux内核是3.5及以后的,最好是设置swappiness=10,不要设置swappiness=0

小结

在内存资源紧张的时候,Linux通过直接回收内存和定期扫描的方式,来释放文件页和匿名页,以便把内存分配给更需要的进程使用。

  • 文件页的回收比较容易理解,直接情空,或者把脏数据写回磁盘后再释放
  • 匿名页的回收,需要通过swap换出到磁盘中,下次访问时,再从磁盘换入到内存中

可以设置/proc/sys/vm/min_free_kbytes,来调整系统定期回收内存的阈值(也就是⻚低阈值),还可以设置/proc/sys/vm/swappiness,来调整⽂件⻚和匿名⻚的回收倾向

在NUMA架构下,每个Node都有自己的本地内存空间,而当本地内存不足时,默认既可以从其他Node寻找空闲内存,也可以从本地内存回收。

可以设置 /proc/sys/vm/zone_reclaim_mode ,来调整NUMA本地内存的回收策略。

  • swap应该是针对以前内存⼩的⼀种优化,不过现在内存没那么昂贵之后,所以就没那么⼤的必要开启了
  • numa感觉是对系统资源做的隔离分区,不过⽬前虚拟化和docker这么流⾏。⽽且node与node之间访问更耗时,针对⼤程序不⼀定启到了优化作⽤,针对⼩程序,也没有太⼤必要。所以numa也没必要开启

案例

当 Swap 使⽤升⾼时,要如何定位和分析呢

准备

  • 机器配置:2 CPU,8GB 内存
  • 需要预先安装 sysstat 等⼯具,如 apt install sysstat

开始

在终端中运⾏free命令,查看Swap的使⽤情况

$ free
       total   used   free  shared  buff/cache available
Mem:  8169348 331668 6715972  696    1121708     7522896
Swap: 0           0     0

从这个free输出你可以看到,Swap的⼤⼩是0,这说明机器没有配置Swap。

为了继续Swap的案例, 就需要先配置、开启Swap。

要开启Swap,我们⾸先要清楚,Linux本身⽀持两种类型的Swap,即Swap分区和Swap⽂件。以Swap⽂件为例,在第⼀个终端中运⾏下⾯的命令开启Swap,我这⾥配置Swap⽂件的⼤⼩为8GB:

# 创建Swap⽂件
$ fallocate -l 8G /mnt/swapfile
# 修改权限只有根⽤户可以访问
$ chmod 600 /mnt/swapfile
# 配置Swap⽂件
$ mkswap /mnt/swapfile
# 开启Swap
$ swapon /mnt/swapfile

然后,再执⾏free命令,确认Swap配置成功:

$ free
      total   used   free   shared buff/cache available
Mem: 8169348 331668 6715972   696    1121708 7522896
Swap: 8388604  0    8388604

现在,free 输出中,Swap 空间以及剩余空间都从 0 变成了8GB,说明Swap已经正常启。

接下来,我们在第⼀个终端中,运⾏下⾯的 dd 命令,模拟⼤⽂件的读取:

# 写⼊空设备,实际上只有磁盘的读请求
$ dd if=/dev/sda1 of=/dev/null bs=1G count=2048

接着,在第⼆个终端中运⾏ sar 命令,查看内存各个指标的变化情况。你可以多观察⼀会⼉,查看这些指标的变化情况。

# 间隔1秒输出⼀组数据
# -r表示显示内存使⽤情况,-S表示显示Swap使⽤情况
$ sar -r -S 1
04:39:56 kbmemfree kbavail kbmemused %memused kbbuffers kbcached kbcommit %commit kbactive kbinact kbdirty
04:39:57 6249676   6839824 1919632    23.50     740512    67316   1691736 10.22 815156 841868 4
04:39:56 kbswpfree kbswpused %swpused kbswpcad %swpcad
04:39:57 8388604     0        0.00      0      0.00

04:39:57 kbmemfree kbavail kbmemused %memused kbbuffers kbcached kbcommit %commit kbactive kbinact kbdirty
04:39:58 6184472    6807064 1984836   24.30    772768     67380   1691736  10.22   847932   874224    20
04:39:57 kbswpfree kbswpused %swpused kbswpcad %swpcad
04:39:58 8388604     0          0.00    0       0.00
…
04:44:06 kbmemfree kbavail kbmemused %memused kbbuffers kbcached kbcommit %commit kbactive kbinact kbdirty
04:44:07 152780    6525716 8016528    98.13    6530440    51316   1691736  10.22    867124   6869332   0
04:44:06 kbswpfree kbswpused %swpused kbswpcad %swpcad
04:44:07 8384508   4096      0.05     52       1.27

我们可以看到,sar的输出结果是两个表格,第⼀个表格表示内存的使⽤情况,第⼆个表格表示Swap的使⽤情况。其中,各个指标名称前⾯的kb前缀,表示这些指标的单位是KB。

去掉前缀后,你会发现,⼤部分指标我们都已经⻅过了,剩下的⼏个新出现的指标,我来简单介绍⼀下

  • kbcommit,表示当前系统负载需要的内存。它实际上是为了保证系统内存不溢出,对需要内存的估计值。%commit,就是这个值相对总内存的百分⽐。
  • kbactive,表示活跃内存,也就是最近使⽤过的内存,⼀般不会被系统回收
  • kbinact,表示⾮活跃内存,也就是不常访问的内存,有可能会被系统回收。

清楚了界⾯指标的含义后,我们再结合具体数值,来分析相关的现象。你可以清楚地看到,总的内存使⽤率(%memused)在不断增⻓,从开始的23%⼀直⻓到了 98%,并且主要内存都被缓冲区(kbbuffers)占⽤。具体来说:

  • 刚开始,剩余内存(kbmemfree)不断减少,⽽缓冲区(kbbuffers)则不断增⼤,由此可知,剩余内存不断分配给了缓冲区。
  • ⼀段时间后,剩余内存已经很⼩,⽽缓冲区占⽤了⼤部分内存。这时候,Swap的使⽤开始逐渐增⼤,缓冲区和剩余内存则只在⼩范围内波动。

你可能困惑了,为什么缓冲区在不停增⼤?这⼜是哪些进程导致的呢?

显然,我们还得看看进程缓存的情况。在第⼆个终端中,按下Ctrl+C停⽌sar命令,然后运⾏下⾯的cachetop命令,观察缓存的使⽤情况:

$ cachetop 5
12:28:28 Buffers MB: 6349 / Cached MB: 87 / Sort: HITS / Order: ascending
PID    UID CMD     HITS  MISSES DIRTIES READ_HIT% WRITE_HIT%
18280 root python   22     0       0     100.0%    0.0%
18279 root dd      41088 41022     0      50.0%    50.0%

通过cachetop的输出,我们看到,dd进程的读写请求只有50%的命中率,并且未命中的缓存⻚数(MISSES)为41022(单位是⻚)。这说明,正是案例开始时运⾏的dd,导致了缓冲区使⽤升⾼。

你可能接着会问,为什么Swap也跟着升⾼了呢?直观来说,缓冲区占了大部分内存,还属于可回收内存,内存不够用时,不应该先回收缓存区吗?

这种情况,我们还得进一步通过/proc/zoneinfo,观察剩余内存、内存阈值以及匿名页和文件页的活跃情况。

可以在第⼆个终端中,按下Ctrl+C,停⽌cachetop命令。然后运⾏下⾯的命令,观察 /proc/zoneinfo 中这⼏个指标的变化情况:

# -d 表示⾼亮变化的字段
# -A 表示仅显示Normal⾏以及之后的15⾏输出
$ watch -d grep -A 15 'Normal' /proc/zoneinfo
Node 0, zone Normal
pages free 21328
min 14896
low 18620
high 22344
spanned 1835008
present 1835008
managed 1796710
protection: (0, 0, 0, 0, 0)
nr_free_pages 21328
nr_zone_inactive_anon 79776
nr_zone_active_anon 206854
nr_zone_inactive_file 918561
nr_zone_active_file 496695
nr_zone_unevictable 2251
nr_zone_write_pending 0

你可以发现,剩余内存(pages_free)在⼀个⼩范围内不停地波动。当它⼩于⻚低阈值(pages_low) 时,⼜会突然增⼤到⼀个⼤于⻚⾼阈值(pages_high)的值。

再结合刚刚⽤ sar 看到的剩余内存和缓冲区的变化情况,我们可以推导出,剩余内存和缓冲区的波动变化,正是由于内存回收和缓存再次分配的循环往复。

  • 当剩余内存小于页低阈值时,系统会回收一些缓存和匿名内存,使得剩余内存增长。其中,缓存的回收导致sar中的缓存区域小,而匿名内存的回收导致了swap的使用增大。
  • 紧接着,由于dd还在继续,剩余内存⼜会重新分配给缓存,导致剩余内存减少,缓冲区增⼤

其实还有⼀个有趣的现象,如果多次运⾏dd和sar,你可能会发现,在多次的循环重复中,有时候是Swap⽤得⽐较多,有时候Swap很少,反⽽缓冲区的波动更⼤。

换句话说,系统回收内存时,有时候会回收更多的⽂件⻚,有时候⼜回收了更多的匿名⻚。

显然,系统回收不同类型内存的倾向,似乎不那么明显。swappiness,正是调整不同类型内存回收的配置选项。

$ cat /proc/sys/vm/swappiness
60

swappiness显示的是默认值60,这是⼀个相对中和的配置,所以系统会根据实际运⾏情况,选择合适的回收类型,⽐如回收不活跃的匿名⻚,或者不活跃的⽂件⻚。

到这⾥,我们已经找出了Swap发⽣的根源。另⼀个问题就是,刚才的Swap到底影响了哪些应⽤程序呢?换句话说,Swap换出的是哪些进程的内存?

⾥我还是推荐 proc⽂件系统,⽤来查看进程Swap换出的虚拟内存⼤⼩,它保存在 /proc/pid/status中的VmSwap中(推荐你执⾏man proc来查询其他字段的含义)。

# 按VmSwap使⽤量对进程排序,输出进程名称、进程ID以及SWAP⽤量
$ for file in /proc/*/status ; do awk '/VmSwap|Name|^Pid/{printf $2 " " $3}END{ print ""}' $file; done | sort -k 3 -n -r | head
dockerd 2226 10728 kB
docker-containe 2251 8516 kB
snapd 936 4020 kB
networkd-dispat 911 836 kB
polkitd 1004 44 kB

从这⾥你可以看到,使⽤Swap⽐较多的是dockerd 和 docker-containe 进程,所以,当dockerd再次访问这些换出到磁盘的内存时,也会⽐较慢。

这也说明了一点,虽然缓存属于可回收内存,但在类似大文件拷贝这类场景下,系统还是会用swap机制来回收匿名内存,而不仅仅是回收占用绝大部分内存的文件页。

最后,如果你在⼀开始配置了 Swap,不要忘记在案例结束后关闭。你可以运⾏下⾯的命令,关闭Swap:

$ swapoff -a

实际上,关闭Swap后再重新打开,也是⼀种常⽤的Swap空间清理⽅法,⽐如:

$ swapoff -a && swapon -a

小结

在内存资源紧张时,Linux会通过Swap,把不常访问的匿名页换出到磁盘中,下次访问的时候再从磁盘中换入到内存中来。可以设置/proc/sys/vm/min_free_kbytes,来调整系统定期回收内存的阈值;也可以设置/proc/sys/vm/swappiness,来调整⽂件⻚和匿名⻚的回收倾向。

当Swap变⾼时,可以⽤ sar、/proc/zoneinfo、/proc/pid/status等⽅法,查看系统和进程的内存使⽤情况,进⽽找出Swap升⾼的根源和受影响的进程。

反过来说,通常,降低Swap的使⽤,可以提⾼系统的整体性能。要怎么做呢?这⾥,我也总结了⼏种常⻅的降低⽅法。

  • 禁止swap,现在服务器的内存足够大,所以除非有必要,禁用swap即可。随着云计算的普及,⼤部分云平台中的虚拟机都默认禁⽌Swap。
  • 如果实在需要⽤到Swap,可以尝试降低swappiness的值,减少内存回收时Swap的使⽤倾向。
  • 响应延时敏感的应用,如果它们可能在开启swap的服务器中运行,你可以用mlock() 或者 mlockall()锁定内存,阻⽌它们的内存换出。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值