1. swap 原理
当系统发生内存泄露,或者运行了占用大量内存的进程,导致系统的内存资源紧张时,会导致两种后果,内存回收和OOM杀死进程。OOM这里就不赘述了,来看看内存回收,也就是系统释放掉可以回收的内存,比如前面讲过的buff/cache,就属于可回收内存。在内存管理中,它们就叫文件页。
大部分文件页都可以被回收,以后有需要时再从磁盘读取就可以了,而那些被程序修改过且暂未被写入磁盘的数据(脏页),就得先写入磁盘,然后才能进行内存释放。这些脏页,一般可以两种方式写入磁盘:
除了buff/cache,通过内存映射获取的文件映射页,也是一种常见的文件页。先释放,再访问时可重新读取。到这里,我们知道了文件页可以被暂时回收供其他使用,那么匿名页呢,比如被应用程序动态分配的堆内存,是不是也可以回收?答案是不可直接回收,但可以暂存到磁盘中,释放给其他的进程使用。这正是Linux系统下的Swap机制。
所谓的Swap机制,就是把一块磁盘或者一个本地文件,当作内存使用,它包括啷个过程:
因此,Swap机制其实扩大了系统的可用内存,即使服务器内存不足,也可以运行大内存的应用程序。比如早年时候,内存太贵,普通学生学习Linux操作系统时,根本用不起大内存电脑,但是可以开启Swap来运行Linux桌面。但是现今时代,内存已经很便宜,Swap是否还有用武之地?当然有。一个很典型的场景,即使内存不足,有些应用程序并不想被OOM机制杀死,而是能希望缓一段时间,等待人工介入,或者等待系统自动释放其他进程的内存,在分配给它。除此之外,我们常见的笔记本电脑的休眠和快速开机功能,也基于Swap。休眠时,把系统内存存到磁盘,再次开机,只要从磁盘加载到内存,这样就省去很多应用程序的初始化过程,加快开机速度。
话说回来,既然Swap是为回收内存,那么何时去回收,又该怎么去衡量内存是否紧张呢?最容易想到的场景,有新的大块内存请求,且内存不足,这个时候系统就要回收一部分内存(比如前面提到的buff/cache),去满足新内存请求,这个过程称为直接内存回收。
除了直接内存回收,还有一个专门的内核线程定期回收内存,也就是kswapd0。为了衡量内存使用情况,kswapd0定义了三个内存阈值(watermark, 水位):页最小阈值(pages_min)、页低阈值(pages_low)、页高阈值(pages_high),剩余内存则使用pages_free表示。下图展示了它们的关系:
kswapd0定期扫描内存的使用情况,并根据 pages_free 落在三个阈值空间的位置,判定回收内存的操作:
我们可以知道,一旦 pages_free 小于 pages_low,就会触发内存回收。这个页低阈值,其实可以通过间接的设置内核选项 /proc/sys/vm/min_free_bytes (页最小阈值)达成。然后其他两个阈值可以根据这个页最小值来计算生成:
pages_low = pages_min*5/4
pages_high = pages_min*3/2
2. NUMA 和 Swap 以及 swappiness
很多情况下,明明发现了swap升高,但是分析系统内存使用情况时,却发现系统剩余内存还多着呢。这正是处理器的NUMA架构导致的。在NUMA架构下,多个处理器会被划分到不同Node上,且每个Node都有自己的本地内存空间。这个内存空间又被分成不同的内存阈(Zone),比如直接内存访问区(DMA)、普通内存区(NORAML)、伪内存区(MOVABLE)等,如下图:
既然NUMA架构下,每个Node都有自己的本地内存空间,那么分析系统内存使用情况,也要从每个Node角度去分析:
$ numactl --hardware
available: 1 nodes (0)
node 0 cpus: 0 1
node 0 size: 7977 MB
node 0 free: 4416 MB
...
结果显示,系统只有一个Node0,编号为0、1的CPU都位于Node0上,且Node0的内存有7977MB,剩余内存有4416MB。
那么这些又跟Swap有什么关系呢?实际上,前面提到的三个内存阈值,都可以通过内存阈在 proc 文件系统中的接口 /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
...
只解释几个比较重要的指标:
当某个Node的内存不足时,系统可以从其他Node寻找空闲内存,也可以在本地回收内存,具体选用哪种模式,可以通过 /proc/sys/vm/zone_reclaim_mode 来调整:
综合上面讲述,知道了Linux下的两种内存回收机制,那么在实际使用是,应该怎么去定优先使用那种回收机制?其实可以配置内核选项,/proc/sys/vm/swappiness 来调整使用 Swap 的积极程度,范围在0~100。值越大,使用Swap越积极,即更倾向于匿名页内存回收,值越小,越消极使用 Swap,也就更倾向于使用文件页回收。注意,这里的0~100不是百分比,而是权重,即使设置为0,即当剩余内存 + 文件页 小于 页高阈值,也仍然会有Swap发生。
3. 实战分析
前面介绍free的时候,应就能看到输出结果中的swap一项,如下:
$ free
total used free shared buff/cache available
Mem: 8169348 331668 6715972 696 1121708 7522896
Swap: 0 0 0
Swap大小是0,说明机器没有配置Swap。下面说说如何开启Swap,首先要知道,Linux本身支持两种类型的Swap,即 Swap分区和 Swap文件。以Swap文件为例,这里配置8GB 的 Swap文件:
# 创建Swap文件
$ fallocate -l 8G /mnt/swapfile
# 修改权限只有根用户可以访问
$ chmod 600 /mnt/swapfile
# 配置Swap文件
$ mkswap /mnt/swapfile
# 开启Swap
$ swapon /mnt/swapfile
$ free
total used free shared buff/cache available
Mem: 8169348 331668 6715972 696 1121708 7522896
Swap: 8388604 0 8388604
Swap空间变为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。这里介绍几个之前没有见过的指标:
再来分析一下相关的现象,总的内存使用率(%memused)在不断增长,从23%涨到98%,并且主要内存都被 kbbuffers 占用。具体来说:
停止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%
可以看到 dd 进程的读写请求只有50%的命中率,未命中的缓存页数(MISSES) 41022 ,可以得出结论,正是dd导致了缓冲区升高。但是,为什么Swap也会跟着升高?缓冲区占了大部分内存,而且是可回收内存,在内存不够用时,不应该优先回收缓冲区吗?
这种情况,我们还得查看 /proc/zoneinfo,观察剩余内存、内存阈值、匿名页和文件页的活跃情况,停止cachetop命令,执行以下命令:
# -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
你会发现,剩余内存在小范围内波动,当它小于页低阈值,又会突然增大到大于页高阈值的一个值。再结合上面sar 和 cachetop的结果综合得出结论,剩余内存和缓冲区的波动变化,正是内存回收和缓存再次分配的循环往复。
还有一个有趣的现象,当多次运行dd 和 sar ,发现有时候Swap用的比较多,有时候又用的比较少,但是缓冲区波动更大。也就是系统回收内存时,有时候回收文件页更多,有时候回收匿名页更多。显然回收不同类型的内存的倾向不明显,去查看swappiness 的配置:
$ cat /proc/sys/vm/swappiness
60
默认值60,这是相对中和的配置,系统会根据实际运行情况,选择合适的回收类型,比如回收不活跃的匿名页,或者不活跃的文件页。
到这里我们已经找出了Swap发生的根源,另一个问题,刚才的Swap影响了那些程序,也就是Swap换出了哪些进程的内存?可以查看 /proc/pid/status,进程Swap换出的虚拟内存大小保存在VmSwap:
# 按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回收匿名内存,而不仅仅是回收占用绝大多数内存的文件页。
最后,案例结束后记得关闭Swap,即 swapoff -a。实际上,关闭再打开,也是一种常用的Swap空间清理方法,即 swapoff -a && swapon -a 。
通常,降低Swap的使用,可以提升系统的整体性能。这里也总结几种降低Swap的方法:
以上是工作之余的学习总结,因CSDN网站的变态要求,这里不能提供内容来源。