Linux 交换空间
在系统管理方面,最早需要做出的决定之一是如何配置交换空间。许多读者可能已经在想他们知道该怎么做:尽可能多地投入 RAM,并配置少量或不配置交换空间。对于许多具有大量 RAM 的系统来说,这非常有效;然而,很少有人意识到 Linux 通过使用一种内存记账形式来实现这一点,这种形式可能会导致生产环境无法接受的系统不稳定性。在本文中,我将解释 Linux 交换系统的基本原理,并展示如何配置交换空间以获得最佳的稳定性和性能。
Linux 是一个按需分页的虚拟内存系统:所有内存都被分成页——几千字节大小的小而相等的块——并且这些块中的大多数可以根据需要换入或换出 RAM(有些页被锁定并且不能被交换)。当一个运行的进程需要的 RAM 比可用 RAM 更多时,一个或多个最近没有使用的 RAM 页会被“换出”以释放 RAM。类似地,如果一个运行的进程需要访问先前“换出”的 RAM,则会换出一个或多个 RAM 页,并将先前换出的 RAM 换入。所有这些都在幕后发生,程序员无需为此担心。
文件系统缓存、程序代码和共享库具有文件系统源,因此与它们相关的 RAM 可以随时重用于其他目的。如果再次需要它们,Linux 可以直接从磁盘中重新读取它们。
程序数据和堆栈空间则不同。这些被放置在匿名页中,之所以这样命名是因为它们没有命名的文件系统源。一旦被修改,匿名页必须在 RAM 中保留到程序结束,除非有辅助存储来写入它。用于这些修改后的匿名页的辅助存储就是我们所说的交换空间。图 1 显示了一个典型的进程地址空间。
这应该立即消除一些常见的误解
交换空间本身并不会降低系统速度。事实上,没有交换空间并不意味着您不会交换页。它仅仅意味着当需求来临时,Linux 对于可以重用哪些 RAM 选项更少。因此,没有交换空间的系统的吞吐量可能低于具有一些交换空间的系统。
交换空间仅用于修改后的匿名页。您的程序、共享库和文件系统缓存在任何情况下都不会写入到那里。
考虑到上面的第 1 项和第 2 项, “最小化交换空间” 的理念实际上只是对浪费磁盘空间的担忧。
在一些按需分页的虚拟内存系统中,操作系统拒绝向程序分配匿名页,除非有足够的交换空间来存储这些页面的修改版本(以便 RAM 可以重用,而程序仍然保持活动状态)。大致的计算是 VM 大小 = 交换空间大小。这提供了两个保证:程序可以访问它们分配的每个虚拟内存字节,并且操作系统始终能够取得进展,因为它可以将一个进程的页换出以供另一个进程使用。
这样做的问题是双重的。首先,程序通常会请求比它们使用的更多的内存。最常见的情况是在进程 fork 期间,此时会使用写时复制匿名页复制整个进程。(写时复制是一种机制,通过该机制,两个进程可以“共享”RAM 的私有可写页。任何一个进程都可以读取该页,但操作系统必须通过给写入者提供该页的新副本以解决写入冲突,从而不与另一个进程冲突。这防止了内核不必不必要地复制数据。)其次,能够将所有匿名页写入交换设备意味着您永远不愿意换出来自文件系统的页(也就是说,您不愿意将文件系统页分配给匿名页,以至于文件系统页可能稍后必须换入)。此类系统通常需要过度配置交换空间才能正常工作。
Solaris 通过允许在匿名页的分配计算中考虑一定量的 RAM(VM 大小 = 交换空间大小 + RAM 大小的百分比)来放宽了这一点。这减少了对交换空间的需求,同时仍然保持了稳定性。如果修改后的匿名页没有足够的交换空间,则当前在 RAM 中的页可以简单地保留在那里,而代码和文件系统缓存页被重用。
Linux 更进一步,放宽了记账规则本身,因此它尝试跟踪 “正在使用” 的内存(图 1 中的非黄色页),而不是通过分配请求承诺的内存。这工作得相当好,因为
许多匿名页永远不会被使用,特别是 fork 期间生成的很少复制的写时复制页。
当内存不足时,可以交换基于文件系统的页。
由于交换程序代码和共享库页而造成的自然减速将阻止用户启动超出系统处理能力的程序。
这与航空公司发放座位预订非常相似。平均而言,一定比例的客户不会出现航班。因此,超额预订确保他们将满载飞行并最大化利润。
类似地,Linux 超额承诺可用的虚拟内存,以尝试最大化您在 RAM 和磁盘空间上的投资回报。不幸的是,如果超额承诺被证明是一个错误,它会 杀死一个(看起来)随机的进程。
公平地说,当内核 知道 内存不足时,该算法会很小心,但这仅在 VM 分配 的增长大致匹配 VM 使用 时才有效。换句话说,如果一个程序分配了大量内存并立即开始写入已分配的页,则该算法在控制事物方面做得很好。如果一个进程分配了大量虚拟内存但没有立即使用它(这在 Java 虚拟机、数据库和其他生产系统中很常见),则该算法可能会分配比它可以使用实际资源支持的虚拟内存多得多的虚拟内存。
此外,许多程序可以优雅地处理拒绝更多内存的情况,例如,数据库具有可调参数,告诉它们为特定任务使用多少 RAM。其他程序可能包含类似这样的内容
buffer = allocate_some_memory(10 MB) if buffer allocation ok sort_using(buffer) else do_a_slower_thing_that_uses_less_memory
但是,Linux 可能会告诉这样一个程序它可以拥有请求的内存,但最终会杀死某些东西以履行该承诺。
幸运的是,有一个内核调优参数可以用于切换内存记账模式。此参数是 vm.overcommit_memory,它指示用于跟踪可用内存的算法。默认值 (0) 使用启发式方法并过度承诺虚拟内存系统。如果您希望您的程序在分配时收到适当的内存不足错误,而不是让您的进程遭受随机杀死,则应将此参数设置为 2。
大多数 Linux 系统允许通过 sysctl 命令(重启后不会保留)或通过将其放在系统启动时应用的文件(通常是 /etc/sysctl.conf)中来调整此参数。要使参数永久生效,请将其添加到 /etc/sysctl.conf
vm.overcommit_memory=2
现在是稍微困难的部分。将 vm.overcommit_memory 设置为 2 后,Linux 将不再分配匿名页,除非它知道它有地方将它们存储在 RAM 或交换空间中。因此,您必须配置足够的交换空间来覆盖它,否则您将无法充分利用您的 RAM,因为它将被保留用于永远不会被使用的东西。数量是困难的部分。您要么必须估计系统的常见负载的匿名页空间需求,要么您需要保守并配置大量交换空间。
关于执行严格 VM 记账的系统的经典建议各不相同,但大多数建议都围绕 “RAM 容量的两倍” 这个数字。该数字假设您的内存主要将充满大量小型交互式程序(其中它们的堆栈空间可能是它们最大的内存需求)。
假设您正在运行一台具有 500 个线程的 Web 服务器,每个线程具有 8MB 的堆栈空间。仅堆栈空间就需要您配置 4GB 的交换空间,才能让内存记账器满意。
磁盘很便宜,所以我通常从 “RAM 容量的两倍” 这个数字开始。一个 16GB 的盒子获得 32GB 的交换空间。我完全预计这对于我的负载来说是过度的,但磁盘性能方面的考虑(大量单独的磁头)意味着我通常拥有比我可以使用的更多的空间。
接下来,我监控系统行为。请记住,交换空间用于记账;我不希望看到太多 I/O 发生在它上面。繁忙系统的交换分区上少量 I/O 不是问题,直到总体吞吐量下降,此时您需要更多 RAM 或更少的程序。
交换空间太少绝对可能成为问题,要么是因为 Linux 拒绝可以轻松满足的内存请求,要么是因为空闲的脏匿名页最终有效地锁定在内存中,并且可能会长期保持如此状态。
性能指标
极佳:甚至没有分配交换空间(free命令显示 0 交换空间使用量),并且 RAM 使用率很低。除非您从巨大的文件系统缓存中受益,否则您可能在 RAM 上花费了太多。运行更多东西。
优秀:已分配交换空间,但交换分区上几乎没有 I/O。
良好:已分配交换空间,并且交换分区上有一些 I/O。系统吞吐量仍然可以。例如,CPU 很忙,并且大部分时间都用于用户或低优先级类别。如果您看到 CPU 等待,则表示磁盘瓶颈(可能在交换上),而系统时间可能意味着操作系统正在疯狂地寻找要重用的内存(页面扫描)。
不佳(交换过多):交换分区上有大量 I/O。CPU 花费大量时间在系统或等待上,并且交换磁盘服务时间超过平均磁盘访问时间。
不佳(交换空间太少):系统运行良好,但新程序因缺少虚拟内存而拒绝启动。
我通常在我的 CentOS 系统上安装 sysstat 软件包,并配置 /usr/lib64/sa/sa1 脚本来收集所有系统数据(包括磁盘 I/O),以便我可以随着时间的推移分析它。我的 crontab 条目是
*/5 * * * * root /usr/lib64/sa/sa1 -d 240 1
这会在每五分钟的时间间隔内收集四分钟的数据。您可以使用名为 kSar (ksar.atomique.net) 的实用程序或在命令行分析结果数据。kSar 的优势在于可以制作比较一个指标与另一个指标的图表。
您可以通过针对这些数据收集文件之一运行 sar 来检查您的历史使用情况。例如,要查看本月 2 号的数据
# sar -f /var/log/sa/sa02
最直接的交换测量来自sar -W:
# sar -W -f /var/log/sa/sa02 00:00:01 pswpin/s pswpout/s 00:10:01 0.00 0.00 ...
原始页数是一个好的开始。如果非零,则您正在进行一些交换。多少是可以接受的更难判断。您需要检查交换分区上的 I/O。为此,您可能需要找出交换设备的主/次设备号。例如,如果您交换到 /dev/sdb1,请使用
# ls -l /dev/hdb1 brw-r----- 1 root disk 8, 17 Dec 1 14:24 /dev/sdb1
找到您的设备号是 8/17。许多版本的 sar 将按主/次设备号报告磁盘 I/O。查看收集的磁盘数据的命令是
# sar -d -f /var/log/sa/sa02 23:15:01 DEV \ tps rd_sec/s \ wr_sec/s avgrq-sz avgqu-sz \ await svctm %util 23:15:01 dev8-17 \ 0.00 0.00 \ 0.00 0.00 0.00 \ 0.00 0.00 0.00
如果您有很多磁盘,请使用 egrep 仅查看标头和设备
# sar -d -f /var/log/sa/sa02 | egrep '(DEV|dev8-17)' ...
任何交换都可能不好,但如果您看到 avgrq-sz(发往设备的请求的平均扇区大小)低于 1,并且等待时间与 svctm(发往设备的 I/O 请求的平均服务时间,以毫秒为单位)大致匹配,您可能还可以。检查系统性能的其他指标,例如 CPU 等待 I/O 和高系统 CPU 时间。
图 2 和图 3 显示了一些使用 kSar 生成的自定义图表,这些图表使事情更容易看清楚。
图 2 是一个比较已用交换空间量与 CPU 花费在等待 I/O 上的时间的图表。存在 I/O 等待的峰值,但它们与交换空间的使用没有任何模式。但是,该系统值得更多关注,因为存在某种 I/O 瓶颈。该系统很可能性能不佳,但这可能不是交换问题(在这种情况下,I/O 等待是繁重的数据库请求负载)。
图 3 显示了一个比较交换设备写入与 I/O 等待的图表(在白天的时间间隔内)。有点难以看清,但 I/O 的红线实际上在整个时间跨度内为零。这表明 I/O 等待与交换无关。请注意,这 不 意味着没有发生任何类型的交换。非匿名页可能已被回收和重新加载。
如果您足够幸运,将所有数据放在一个分区上,并将所有代码放在另一个分区上,则可以通过观察您的 “代码” 分区上的读取来分析这种分页。读取将指示启动时的程序加载(在您的系统中可能不频繁)以及由于内存压力导致的分页。如果读取对应于大量的 I/O 等待,您可能会从更多 RAM 中受益。
我想谈的最后一个参数可能会影响某些负载的响应速度。您是否曾经将系统闲置几个小时,然后返回发现需要很长时间才能再次开始响应?这可能是正在发生的事情:当您离开时,一个 cron 作业(如 updatedb)扫描了文件系统,并且因为您没有使用计算机,所以您的 “非活动” 程序的所有代码页都被抛出,以支持磁盘缓存!有一个名为 vm.swappiness 的内核参数会影响系统抛出代码页而不是其他页的趋势。
在大多数系统上,vm.swappiness 的默认值为 60。将此参数更改为 0 将阻止内核为文件系统缓存抛出代码页,并防止您的桌面由于未监控的文件系统扫描而变得像僵尸一样。
默认值对于生产系统来说很好,因为任何大量使用的服务都会由于其活动而保留在内存中。这允许文件缓存扩展到任何真正未使用的内容上,并且通常会更好地工作。如果您非常确定程序的大部分内存应该保留(例如,您运行一个执行大部分自身缓存的数据库),则调整此参数可能是有利的。在具有一组非常活跃的程序的系统上,增加此参数到 100 也可能有所帮助。
与任何性能参数一样,拥有一些模拟您的实际工作负载的基准测试有助于您可以衡量效果。
调整这些参数将使虚拟内存系统提供更稳定的基础,特别是对于运行消耗大量内存的程序的生产系统。
Tony Kay 从事 UNIX 和 Linux 系统工作超过 20 年。他住在俄勒冈州本德市,在那里他管理大型 Linux 系统并开发移动应用程序。