ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Linux Swap 从原理到实践:触发机制、配置排障与性能调优

Linux Swap 从原理到实践:触发机制、配置排障与性能调优 正经跑过几年Linux服务器的人基本都躲不开 swap 这几个字。我刚入门那会儿觉得交换分区就是拿硬盘凑内存哪台机器 swap 占用高了立刻断定内存不够用得赶紧加内存条。这个想法不算全错但确实太粗糙了。Linux 中的 Swap 交换分区本质上是内存溢出的安全阀它负责在业务高峰期扛住突发内存压力在后台任务中容忍内存闲置在休眠时承载整个内存快照。它不是你该恐惧的东西而是内核内存管理里的必要组件。真正的问题从来不是用了 swap而是swap 用了之后性能掉了多少以及 swap 占据高位时系统到底还撑不撑得住。这篇内容不打算照着手册念我会先从内核视角讲清 swap 的触发机制再把手把手创建 swap、管理 swap 的步骤写完整接着把生产环境最常见的swap 用满但 available 还剩很多做一次完整排障推演最后聊聊 zswap、swappiness 调优、多 swap 设备优先级这些日常没人细讲的话题。无论你是刚碰 Linux 的运维新人还是已经部署过几十台服务器的老手应该都能在里面找到自己关心的点。1. Swap 不是假内存先搞懂内核到底为什么需要它1.1 从一次 Swap 告警说起有一次监控弹告警说某台机器 swap 使用率到了 98%我 SSH 上去执行free -h结果是这样的total used free shared buff/cache available Mem: 15Gi 8.3Gi 1.2Gi 456Mi 6.1Gi 6.4Gi Swap: 8.0Gi 7.8Gi 236Mi当时我脑海里第一反应是内存是不是不够了可仔细一看available 还有 6.4Gi。这就有意思了物理内存明明还能分出去 6.4G为什么 swap 会被写满后来我意识到这是很多刚接触 Linux 性能调优的人最容易卡住的地方swap 使用率不等于当前内存压力。swap 里的内容大多是历史写入的匿名页只要没有新的换入换出动作它们躺在磁盘或SSD上并不怎么消耗CPU也不会让系统明显变慢。判断内存是否真的紧张要看的不是 swap 占用率而是换页频率和相关内存压力指标。这个结论后面会展开讲。1.2 页缓存与匿名页内核的先易后难策略想理解 swap必须先理解 Linux 内核把物理内存分成哪两类看待。一类是页缓存page cache也就是文件读写的缓存。程序读文件、写文件时内核会把文件页放在内存里下次访问直接命中不再走磁盘。这类页在内存紧张时可以被直接回收干净页丢掉即可脏页先写回再丢掉成本低、效果好。另一类是匿名页anonymous pages比如进程的堆、栈以及私有内存映射。这类页没有磁盘上的文件可以对应内核不能简单丢掉否则数据就丢了。想释放匿名页只有两条路要么让进程主动释放free要么把内容写到 swap 设备上再释放物理页。第二条路就是我们说的 swap-out。那么内核在内存不足时会怎么做它会优先回收容易摘的果子。页缓存能回收就先回收页缓存脏页写回时再释放当页缓存回收速度跟不上分配需求或者可回收的页缓存已经明显不足时kswapd内核线程才会更积极地把匿名页换出到 swap。换句话说swap 是内存回收链条里排在页缓存之后的第二选项也是进程内存的唯一外置仓库。Linux 内核还维护着 per-node 的活跃/非活跃 LRU 链表匿名页和文件页各自有独立链表。每次回收时内核会根据swappiness参数调整匿名页和文件页的扫描比例而不是一刀切。默认swappiness60意味着更容易倾向回收文件页但也不是完全不动匿名页。1.3 什么情况下 Swap 会持续增长Swap 的增长曲线既然是判断问题的重要依据那么哪些场景会导致它一直涨我总结下来大致有几类内存泄漏进程申请内存后不释放RSS 一路走高最终把内存耗尽内核开始把它的匿名页换出去。这种场景下 swap 增长是被动兜底。突发内存峰值后的历史留存大批任务跑完匿名页被换到 swap任务结束后这些页仍然躺在 swap 里没有被换回。因为换回也需要成本内核不会闲得没事把 swap 里的页预读回内存只有进程真正去访问被换出的页并且触发缺页异常时才会逐页换入。swappiness 设置过高比如设成 100 甚至有人直接在启动参数里乱填导致内核在尚有大量页缓存可回收时仍然频繁把匿名页换出。cgroup 限制收紧容器或 systemd 服务被加了内存和 swap 上限进程实际可使用的内存范围被压缩内核会按照该控制组的比例进行回收即使宿主机整体内存还富余该 cgroup 内的进程也可能被提前换出。长期闲置的后台进程内核的回收算法会认为很久没访问的匿名页比页缓存更不值得留在内存里把这类进程冷页换出释放物理内存给更热的页。所以swap 持续增长不一定等于系统正在崩溃。更准确的判断方式是结合换页速率、OOM 记录、内存压力指标一起看而不是只看百分比。后面专门有一章会讲这套完整方法。2. 从零创建一支可用的 Swap分区、文件、参数一整套2.1 Swap 的三种形态分区、文件、zram 怎么选创建 swap 之前先得明白当前环境适合用哪种形态。我在不同场景下都用过给它们排了个对比表形态优点缺点典型场景swap 分区不经过文件系统路径固定性能稳定传统工具链成熟创建前要重新分区扩容麻烦物理机、KVM 虚机swap 文件创建、删除、扩容都极其方便不用动分区表多一层文件系统开销路径拼错会导致启动项失效云服务器、Docker 宿主、临时应急zram把内存页压缩后放内存里无物理 IO速度极快本质还是抢内存不适合内存已经很小、CPU 又弱的机器嵌入式、桌面 Linux、轻量虚机现在云服务器基本都是 swap 文件为主因为它不需要重新做系统盘分区文件多大可以随时调整。嵌入式板子上我反而更推荐 zram后面性能章节会详细讲。2.2 实操用 Swap 文件快速配置假设一台新机器物理内存 16G想配 8G swap。我先给一个通用参考值这个值不是拍脑袋物理内存 4G 以下swap 设为内存的 1~2 倍4G~16Gswap 设为内存的 0.5~1 倍16G~64Gswap 设 8~16G 即可64G 以上一般 8~16G 够用除非跑休眠或特殊业务。生产环境不迷信公式先按参考值配然后跟踪一段时间的内存峰值再决定加还是减。创建 swap 文件的标准流程如下# 生成 8G 文件bs1M 比 fallocate 更稳妥 dd if/dev/zero of/swapfile bs1M count8192 statusprogress # 立刻保护文件权限避免普通用户读到 chmod 600 /swapfile # 格式化为 swap 文件系统 mkswap /swapfile # 临时启用 swapon /swapfile # 确认 swapon --show为什么我推荐用dd而不是fallocatefallocate更快但在某些文件系统上创建的预分配文件可能带 extents 空洞或存在与 swap 子系统的兼容性问题极少数情况下mkswap会不认或者运行时触发 IO 异常。用dd写全零文件会慢一点但生产环境求稳这几分钟值得花。永久生效需要写入/etc/fstabecho /swapfile none swap sw 0 0 /etc/fstab这里有个细节swap 文件最好放在根目录或固定目录不要放在会被系统盘自动重新挂载的路径上。否则启动时若文件系统尚未挂载swapon -a会因为找不到文件而报错系统仍然可以启动但 swap 会静默失效直到你排查时才想起来。另外完全没有 swap 的机器上如果想加一块独立分区作为 swap也建议用 UUID 而不是设备名。设备名在重启后可能因磁盘枚举顺序变化而漂移UUID 则不会blkid /dev/sdb3 # 输出类似 UUIDxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx TYPEswap然后在/etc/fstab里写UUIDxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx none swap sw 0 02.3 Swappiness 到底怎么调才合适vm.swappiness配置文件路径在/proc/sys/vm/swappiness范围 0~100默认 60。它的含义是内核回收内存时偏向回收匿名页的倾向程度值越大越积极把进程内存换出到 swap值越小越偏向回收页缓存。很多人有个误解swappiness0就是完全禁用 swap。实际上在主流内核上0 意味着尽量不主动换出匿名页但当内存极端不足、页缓存又回收不动时内核仍会 swap-out。真正想完全不碰 swap只有不建 swap 或对特定 cgroup 做限制。各场景的参考值场景推荐 swappiness说明数据库服务器1~10尽量保住热内存宁可让页缓存先被压缩普通 Web/API 服务器10~20平衡性能和空闲页驱逐桌面 Linux20~30zram 可用 80~100桌面系统有大量冷页可被压缩构建机/批处理机器30~60允许后台任务内存被换出也挺好改法很简单# 临时生效 sysctl vm.swappiness10 # 永久生效写入 /etc/sysctl.conf 或 /etc/sysctl.d/99-swap.conf vm.swappiness10改完后可以用sysctl vm.swappiness验证。2.4 Swap 的启停、刷新与持久化检查日常运维里我们经常要临时关闭或者重建 swap。关闭 swap 的命令是swapoff /swapfile如果要全部关闭swapoff -a这里有个坑swapoff会把 swap 中存活的页面全部换回物理内存。如果当前内存本来就紧张执行swapoff -a轻则卡顿半天重则直接触发 OOM 把关键进程杀掉。所以我建议在内存至少有 10%~20% 空闲时再做或者可以分两步先挤出一部分内存再执行 swapoff。生产中如果要缩容 swap 文件不要直接swapoff大文件然后重建稳妥流程是创建一个小 swap 文件并swapon启用等待几秒让内核把一部分页从小文件上换走swapoff大文件删除大文件。这样能避免一次性换入过多数据造成内存瞬时峰值。另外改了/etc/fstab以后一定要执行swapon -a测试是否能正常加载。我见过太多配置完没验证、重启才发现 swap 没生效的情况这类问题排查起来不复杂但属于典型的低级失误。3. Swap 用满但 available 还剩很多一个经典迷局的完整排障3.1 第一步先把内存统计口径对齐遇到 swap 99% 的告警不要急着说加内存先把下面这几条命令的输出对齐才能知道当前系统到底处在什么状态free -h cat /proc/meminfo | grep -E SwapTotal|SwapFree|MemFree|MemAvailable|Dirty|AnonPages|Mapped cat /proc/vmstat | grep -E pswpin|pswpout cat /proc/pressure/memory关键点在于free输出里的available是内核估算的、在不触发 OOM 的情况下还能分配给新进程的内存量它包含了可回收的页缓存和部分内核 slab 缓存。而 swap 的使用量是历史上被换出的匿名页总量。两者虽然都展示在同一个界面上计算逻辑完全不同。如果pswpin和pswpout很小说明 swap 几乎没有换页动作只是存量高。这就像一个仓库里堆了很多旧箱子但搬运工都没在干活系统自然不卡。反过来说只要pswpout数值在快速增长哪怕 swap 使用率只有 30%也比swap 99% 但零换页更值得警惕。3.2 第二步定位到底是谁霸占了 swap如果 swap 里确实躺着大量页面下一步就应该找到是哪些进程的匿名页被换出去了。Linux 没有一条命令直接给人看进程 swap 占用排行榜最可靠的做法是遍历/proc/*/status里的VmSwap字段for p in /proc/[0-9]*; do pid${p##*/} awk -v pid$pid /Name/{name$2} /VmSwap/ $200 {printf %s %s %d KB\n, name, pid, $2} $p/status done | sort -k3 -nr如果系统装了smem会更简单smem -t -k -s swap | head -20定位到进程后再看它是正常业务还是异常进程。常见的情况是一个 Java 服务占了几十 G 堆内存但 JVM 堆很久没有 GC 清理内部堆对象没有被实际访问被内核当成了冷匿名页换出。这种进程的 swap 占用高其实是正常现象不代表故障。3.3 第三步逐个排查根因我比较习惯按下面这个顺序逐项排查一、历史压力残留型free显示 available 高vmstat里si/so几乎为 0进程列表中 swap 占用稳定。这种情况多数是之前有过内存峰值峰值过后内核没有主动把换出页换回。不需要处理但要在监控上记录基线下次再来对比。二、cgroup 边界限制型容器和 systemd 服务都可能有独立的内存控制组。比如 Docker 的--memory-swap限制或者 systemd 单元里设置了MemorySwapMax都会让特定进程在宿主机内存还富余时被强制换出。排查方法# cgroup v2 下查看当前 cgroup 的 swap 占用和上限 cat /sys/fs/cgroup/memory.swap.current cat /sys/fs/cgroup/memory.swap.max # 看看 systemd 某个服务的 slice cat /sys/fs/cgroup/system.slice/xxx.service/memory.swap.current如果某个进程的 swap 占用就卡在 cgroup 限制值附近那就实锤了。解决方式是调整服务的 MemorySwapMax或审视为何给这个服务这么小内存上限。三、swappiness 设置过高型如果系统vm.swappiness被调得过高比如 100内核对匿名页的回收会太积极。此时即使 page cache 还能回收它也会倾向于换出匿名页让物理内存变成一大片缓存。这种情况直接调低 swappiness 即可。四、透明大页与内存碎片型这类问题最容易被人忽略。透明大页THP开启后内核会尝试分配 2MB 的连续物理页给进程。物理内存长期运行后出现碎片无法满足连续分配内核会触发内存回收甚至把匿名页换出到 swap以腾出连续页。排查方式dmesg出现大量page allocation failure相关记录或者grep -i thp /proc/vmstat显示的thp_collapse_alloc_failed持续增加就要怀疑 THP 和碎片问题。临时可以关闭 THPecho never /sys/kernel/mm/transparent_hugepage/enabled不过关闭 THP 是有代价的大内存分配场景可能要重新评估性能建议先在测试环境验证再决定是否在生产关闭。3.4 系统性解法与长期防御swap 用满但 available 高这个现象我的判断方法是如果没有持续换页不当作紧急故障如果伴随频繁换页则按内存压力处理。但无论是否紧急长期防御里我都建议做这几件事统一把vm.swappiness调低到 10减少内核主动换出匿名页的意愿给重要服务设置 cgroup 内存上限确保单个进程不能拖垮整机监控里同时盯 swap 使用率、pswpin/pswppout、PSI 内存压力三项而不是只盯 swap 百分比对不需要 swap 保护的容器明确设置memory.swap.max0让它直接 OOM 被杀掉重启而不是在 swap 里挣扎拖慢所有 IO。当然直接把 swap0 也不是万能解。有些业务进程被杀后重启成本极高宁可让它把匿名页换出、熬过峰值再说。这种业务应该保留 swap只是把 swappiness 压低并且要给足物理内存或做好自动扩容。4. Swap 的性能账本写入放大、zswap 与多盘设置4.1 为什么 Swap 的访问模式那么伤磁盘很多人觉得反正有 swap 兜底慢一点没关系但真到 swap 大量换页的时候慢的程度可能远超预期。swap 的基本操作粒度是 4KB而且访问模式是随机的。进程访问的匿名页没有明显顺序性哪个页被访问到缺页异常就会去 swap 设备上读哪个页。机械硬盘随机 4K 读写的 IOPS 只有几十一旦系统进入持续 swap-in/out磁盘寻道时间会吃掉几乎所有吞吐整机瞬间像死机一样。即便是 SSD虽然随机 4K 性能好得多但闪存最小擦除单位往往大于 4KB频繁小块写入会产生写放大效应对闪存寿命也是压力。所以我不是反对SSD 上开 swap而是反对把 swap 当作正常内存路径依赖。4.2 zswap用压缩换内存的性价比方案如果你不想完全放弃 swap 的兜底能力又想减少磁盘 IOzswap 是很好的中间方案。zswap 是内核的一个压缩 swap 缓存模块。流程是这样的匿名页要换出时zswap 先把页内容压缩存到内存里一块专用池中不立即写磁盘。当内存又变大、或者这个压缩页被再次访问时就直接从压缩池中解压省掉一次磁盘 IO。只有内存压力继续加大淘汰的压缩页才会真正写入后端 swap 设备。这相当于给 swap 加了一层CPU 压缩内存缓存对大量不常被访问的冷页效果尤其明显。启用方式# 查看是否开启 cat /sys/module/zswap/parameters/enabled # 设置相关参数 echo lz4 /sys/module/zswap/parameters/compressor echo z3fold /sys/module/zswap/parameters/zpool echo 20 /sys/module/zswap/parameters/max_pool_percent # 开启 echo 1 /sys/module/zswap/parameters/enabled如果内核已经编译了 zswap也可以直接在启动参数里加zswap.enabled1 zswap.compressorlz4 zswap.zpoolz3fold需要提醒的是zswap 的压缩池本身也占用内存max_pool_percent默认约 20% 内存上限需要预留一定内存给它。CPU 太弱的嵌入式设备不要盲目开 zswap可能压缩开销比 IO 节省还高。zram 和 zswap 的名字很像但不是一个东西。zram 是把一整块内存当作压缩块设备直接拿来做 swap完全不会写入磁盘zswap 则是作为 swap 的压缩前端最终还有可能写回后端物理 swap。两者适合不同场景我一般在桌面机和嵌入式上用 zram在已经有 swap 分区/文件的服务器的加 zswap 作为加速层。4.3 多 Swap 设备与优先级容量与性能可以兼得当机器上有多个磁盘或分区可以做 swap 时可以用swapon -p设置优先级。数字越大优先级越高内核会优先把匿名页换出到高优先级设备。一个比较合理的组合是NVMe 上一个小的 swap优先级设为 10SATA SSD 上一个大的 swap优先级设为 5HDD 上原则上不建议放 swap如果必须放优先级继续往下调。这样既能保证常见的换页落在高速设备上又能在极端情况下有容量兜底。swapon -p 10 /dev/nvme0n1p2 swapon -p 5 /dev/sda2 swapon --show4.4 一个深刻的教训Swap 放在 HDD 上到底有多痛早年我在一台老服务器上吃过苦头。那台机器是 SATA 机械硬盘内存 16G我图省事直接创建了一个 8G 的 swap 文件没考虑磁盘性能。某个周末离线统计任务跑起来内存瞬间吃完内核开始大量 swap-out。结果就是top 里所有进程 CPU 使用率骤降load average 飙升到几十但 IO 等待时间接近满值。机械硬盘随机写 4K 页完全顶不住。后来我把 swap 文件缩到 2G把 swappiness 调成 1再给离线任务加内存限制才算压住。那次之后我的原则就变成了swap 可以配但必须知道它的载体性能HDD 上的大 swap 不但不能兜底反而可能把整台机器拖进 IO 风暴。5. 监控 Swap 的日常与不同场景的配置清单5.1 一套命令快速摸清 Swap 现状我每次排查内存问题时都会用一个固定命令组合模板能在一分钟内给出大致结论# 1. swap 设备和容量 swapon --show # 2. 内存总览 free -h # 3. 内核内存细节 cat /proc/meminfo | grep -E SwapTotal|SwapFree|MemTotal|MemFree|MemAvailable|Dirty|AnonPages|Mapped # 4. 换页速率si 表示 swap inso 表示 swap out vmstat 1 5 # 5. 内存压力记录 grep -E pswpin|pswpout /proc/vmstat如果so持续大于 0说明正在发生 swap-out如果si持续大于 0说明进程正在大量从 swap 里换入内存这种场景往往是临时体验最差的时候。再结合dmesg | tail -100看有没有 OOM 或页面分配失败基本就能定位了。5.2 用 PSI 和内存压力指标判断真的紧张还是提前换出PSIPressure Stall Information是判断内存真实紧张程度的利器。内核会统计因为等待内存资源导致任务阻塞的时间比例输出一般在/proc/pressure/memory内容类似some avg100.00 avg600.00 avg3000.00 total123 full avg100.00 avg600.00 avg3000.00 total98some表示至少有一个任务因内存而阻塞的时间比例full表示所有任务都被阻塞的时间比例。如果some avg10很高且伴随si大量增长这才是需要立刻介入的信号。如果some/full都接近 0只是 swap 使用量维持在高位那基本属于历史压力留存不值得半夜爬起来处理。5.3 几类典型场景的 Swap 配置参考不同业务的 swap 策略差异很大我整理了一个相对通用的配置表可以作为初始值场景swap 大小swappiness附加项MySQL/PostgreSQL 等数据库不配或 4~8G1关闭 THP大页交给数据库自己管理普通 Web/API 服务内存的 0.5~1 倍上限 16G10配合 systemd 内存限额构建机/离线批处理内存等量30允许冷页换出换页面高峰前错峰桌面 Linuxzram swap 文件 8G80~100zram 默认优先辅助 swap 兜底容器宿主宿主保留 2~4G10每个容器单独设 memory.swap.max嵌入式/ARM 板子zram 即可80~100尽量避免物理磁盘 swap对数据库场景很多人直接把 swap 彻底关掉理由很简单数据库的 Buffer Pool 是明确申请的大块内存它自己管理比内核换页聪明得多swap 只会干扰数据库的行为。但我的建议是保留少量 swap例如 4G 左右并把 swappiness 压到 1。这样在极端内存峰值时数据库不至于被瞬间 OOM 杀死而是有极有限的喘息空间。永远记住OOM 是杀进程swap 是慢慢比死好处理。最后分享一个实战技巧新机器上线后先按物理内存 0.5~1 倍上限 16G配好 swap再用一段时间记录内存峰值和 swap 峰值。如果没有出现 swap-out 速率暴涨就维持现状如果频繁换页则要优先减 swap、调低 swappiness然后从业务侧排查内存分配是否合理。swap 这东西不是配一次管一辈子它和业务模型紧密相关每次服务架构变化后都值得重新审视一遍。
返回列表