凌晨两点被监控告警叫起来,登录服务器一看,Swap 使用率 98%,内存也快见底了。这种时候最怕的就是手里没有一套稳妥的“在线扩容”方案——不能重启,不能影响正在跑的业务,最好能在几分钟内把 Swap 空间撑大。这活儿我干过不止一次,踩过不少坑,今天把 swap 在线扩容的方法完整梳理一遍。从判断当前 swap 形态开始,到 swap 文件、LVM 逻辑卷、物理分区三种场景的实操步骤,再到 swapoff 的隐藏风险、优先级调整、开机自启这些细节,一次性讲透。
1. 动手之前先搞清楚你手上的swap是什么形态
很多人上来就敲命令,敲了一半发现报错,根本原因是连自己机器上跑的是哪种 swap 都没搞清楚。swap 在 Linux 里一共有三种存在形态:独立的 swap 分区、LVM 逻辑卷上的 swap、以及一个普通文件作为 swap。三种形态的扩容方式完全不同,搞混了轻则白忙活,重则把系统搞到起不来。
1.1 三种形态一眼分辨
最简单的判断方法就一条命令:
swapon --show输出里 NAME 和 TYPE 两列是关键。TYPE 显示partition的是物理分区,显示file的是文件型 swap,LVM 逻辑卷在部分发行版里会直接显示lvm,但有些系统显示的还是partition。为了确认,再用lsblk -f看一眼设备全貌,如果某个设备挂在vg-开头的卷组下面,那基本就是 LVM 形态没跑了。
这里有个很容易忽略的细节:free -h只能看到总量,看不出形态。很多云主机默认就是文件型 swap,路径通常是/swapfile或者/swap.img。物理机上最常见的则是独立分区/dev/sda3之类。LVM 形态常见于自建机房的机器,路径一般长这样:/dev/mapper/vg_swap-lv_swap。
把那台机器的现状彻底看清楚,后续选方案才有依据。建议顺手把这几条都跑一遍:
free -h swapon --show blkid | grep -i swap df -h最后一条df -h是为了确认根分区或者/var目录有没有足够的剩余空间。如果打算走文件型 swap 路线,空间不够一切都是空谈。
1.2 在线扩容的本质:加新比改老更稳
“在线扩容”这四个字本身有歧义。严格意义上的实时扩容,指的是不重启、不中断业务、在当前系统运行状态下把可用 swap 总量变大。对于文件型 swap,你完全不需要动旧的 swap 文件,直接新建一个大文件,格式化后启用,总量立刻变大——这才是真正零风险的在线扩容。
但如果你非要“扩展现有的 swap 区域本身”,那对不起,swap 的格式是裸设备,没有类似 resize2fs 那样的在线调整工具。你必须把这个 swap 停掉再重建,而这个“停掉”的动作就叫 swapoff。swapoff 不是随便敲的,它有风险,后面我会专门开一节细讲。
所以我的核心观点先放在前面:在线扩容首选“新增文件/新增强度”,不要去“改老卷大小”。你保留原来的 swap 不动,新增一个 8G 的 swap 文件,通过优先级设置让系统优先使用新的大空间,旧空间慢慢就腾出来了。整个过程没有任何一个瞬间是 swap 容量减少的,业务完全无感。
2. swap文件扩展:五分钟操作,适用大多数云主机
文件型 swap 是云服务器上最主流的形态,也是在线扩容最友好的场景。原因很简单:扩容一个文件比扩容分区表容易一万倍。即使你的系统目前用的是 swap 分区,只要磁盘上有空间,我一般也建议直接新建 swap 文件来补充,而不是去动分区。下面按完整步骤走。
2.1 创建大文件:dd还是fallocate
创建 swap 文件最常见的是两个命令:fallocate和dd。两者的区别值得说道说道。
# 方法一:fallocate,瞬间完成 fallocate -l 8G /swapfile1 # 方法二:dd,慢但稳 dd if=/dev/zero of=/swapfile1 bs=1M count=8192 status=progress我踩过坑的地方就在这里。fallocate分配的是“预留空间”,并没有真正写入数据,这在个别文件系统上会出问题。最典型的现象就是mkswap时提示swapfile has holes,然后swapon直接拒绝启用。我在某云主机的根文件系统(xfs)上真实遇到过。所以我在生产环境上的规则很简单:
- ext4 上用 fallocate 基本没问题,但保险起见我仍然用 dd
- xfs、btrfs 上不要用 fallocate 创建 swap 文件,大概率翻车
- dd 慢归慢,但它真正把磁盘块写了一遍,绝对不会有 holes 问题
如果你用 dd 创建 8G 文件嫌慢,有个折中:用dd配合status=progress观察进度,实测在普通 SSD 上写 8G 也就十几秒,机械盘稍微慢一点,但完全可以接受。
文件创建完之后,强烈建议立刻改权限。swap 文件里保存的是内存里换出来的数据,可能有敏感信息,权限必须是 600:
chmod 600 /swapfile12.2 格式化、启用、持久化一条龙
接下来把文件格式化成 swap 格式并启用:
mkswap /swapfile1 swapon /swapfile1做完之后用free -h或者swapon --show确认,你就会发现 Swap 总量明显变大了。这一步完成,其实已经算是扩容成功——不需要重启,当前正在跑的进程完全不受影响。
但这里必须强调:没写进 /etc/fstab 的 swap,重启后会丢。很多新手在这个环节掉坑里:今天扩容成功了,下个月重启发现 swap 又变回去了。持久化配置这样写:
echo '/swapfile1 none swap sw 0 0' >> /etc/fstab两个细节值得说:
第一,可以用 UUID 代替路径。mkswap之后用blkid /swapfile1能看到这个文件的 UUID,写成UUID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx none swap sw 0 0更稳。路径写法的风险是如果哪天文件被误删,开机时会卡在 swap 等待上;UUID 同理。更稳妥的做法是给 fstab 行加nofail选项:
echo '/swapfile1 none swap sw,nofail 0 0' >> /etc/fstab加了这个选项,哪怕文件出问题,开机最多打一条警告,不会卡住系统。生产环境我必加。
第二,fstab 修改后不要急着重启验证。用swapon -a手动挂载所有 fstab 里的 swap 条目,能成功就说明配置没问题。这个命令防呆效果极好。
2.3 想替换旧swap?先学会安全交换
新增文件扩容是最佳路线,但有些场景你确实需要把旧的 swap 替换掉。比如原来某个 swap 文件才 1G,你想把它回收,换成 8G 的新文件。这时候就必须swapoff /swapfile把旧的停掉。
安全交换的完整动作是这样的:
# 1. 先确认内存是否足够 free -h # 2. 把旧的 swap 停掉,这一步看 SwapUsed 是多少 swapoff /swapfile # 3. 删除旧文件或原地重建 rm -f /swapfile # 4. 重建新的 dd if=/dev/zero of=/swapfile bs=1M count=8192 status=progress mkswap /swapfile swapon /swapfile关键判断在第二步。用free -h看 Swap 那一行的 used 值,确保当前可用内存(available 列)比这个值大得多,再执行 swapoff。swapoff 的机制是把 swap 空间里的内容写回内存,内存撑不住就是灾难。我见过一台内存压力很大的机器执行 swapoff 之后,直接卡死,最后被 OOM killer 干掉了一个关键进程。
如果你的 swap used 很高、内存又紧张,不要硬上。正确姿势是:新增一个更大的 swap 文件并设高优先级(下面会讲),让系统把数据先“挪”到新空间,等到旧 swap 的 used 降到接近 0 再 swapoff,风险瞬间小很多。
3. LVM逻辑卷和物理分区的swap扩容
文件型 swap 讲完了,但还有相当多的服务器是用 LVM 逻辑卷或独立物理分区承载 swap。这两种场景麻烦一点,但也不是不能在线搞定,关键是步骤顺序不能错。
3.1 LVM场景:lvextend之后必须重建swap区域
LVM 最大的好处是卷可以热扩。如果 swap 跑在 LVM 逻辑卷上,扩容逻辑卷本身很简单:
# 先看卷组和逻辑卷名 lvs # 扩展逻辑卷,增加 4G lvextend -L +4G /dev/mapper/vg_swap-lv_swap执行完lvextend之后,逻辑卷本身已经从 4G 变成 8G。但此时 swap 区域并不知道这件事,它内部还是旧大小。这是因为 swap 没有文件系统,不存在“扩展文件系统”这一步,你必须重新格式化 swap 区域:
swapoff /dev/mapper/vg_swap-lv_swap mkswap /dev/mapper/vg_swap-lv_swap swapon /dev/mapper/vg_swap-lv_swap这里有一个绝对不能踩的坑:LVM 扩容后的设备路径是不会变的(除非你把逻辑卷删了重建),但如果你在 fstab 里用的是 UUID 而不是设备路径,那么mkswap之后 UUID 已经变了,必须去更新 fstab。用blkid查新 UUID 再改,否则重启必卡。
# 用这条查新 UUID blkid /dev/mapper/vg_swap-lv_swap至于前面说的 swapoff 风险,LVM 场景同样适用。如果 swap 使用率不高,直接 swapoff 没毛病;使用率高的话,还是建议先把内存压力降下来再操作。
另外一个实用技巧:如果目标机器上有空闲的物理卷或者卷组剩余空间充足,你完全可以不扩展现有 swap 逻辑卷,而是新建一个逻辑卷作为额外的 swap。这样连 swapoff 都可以省掉:
lvcreate -L 8G -n lv_swap2 vg_swap mkswap /dev/mapper/vg_swap-lv_swap2 swapon /dev/mapper/vg_swap-lv_swap2新增逻辑卷对现有逻辑卷完全没有影响,整个过程就是纯在线扩容,一次 swapoff 都不用执行。LVM 场景我强烈推荐这种做法。
3.2 非LVM物理分区的扩容:能做但不推荐
如果你的 swap 是一个物理分区,比如/dev/sda2,那扩容就麻烦得多。麻烦的核心在于分区表。你要扩大这个分区,通常得删除分区记录再重建一个更大的分区,起始扇区必须保持一致,结束扇区往后推。这一套操作一旦失误,影响到的不只是 swap 分区,而是整个磁盘的分区表。
我直接给结论:非 LVM 的物理 swap 分区,如果磁盘后面还有空闲空间,优先考虑新建一个独立 swap 分区,而不是去拉伸旧分区。新建分区的操作其实很常规,fdisk 里n新建,类型选 82(Linux swap),写完w保存,然后mkswap启用。整个过程比修改旧分区安全得多。
如果确实没有额外分区空间,只能拉伸旧分区,那标准流程是这样:
# 1. 停用 swap swapoff /dev/sda2 # 2. 用 parted 或 fdisk 调整分区,起始扇区不变,结束扇区扩大 # 3. 让内核重新读取分区表 partprobe /dev/sda # 4. 重新格式化 swap 并启用 mkswap /dev/sda2 swapon /dev/sda2现实中还有一个更“离线”的场景:某些云平台上云盘扩容后,分区表的变化需要重启才完全稳定。所以物理分区 swap 的扩容,其实很难真正实现“完全在线”。这也是我把“尽量别走这条路”写在前面原因。
4. 在线扩容的边界条件和运维细节
前面讲了具体操作,这一节专门讲那些容易踩的边界和细节。很多人 swap 扩容做完了,当时一切正常,过几天出问题才想起来哪个细节没注意,说的就是这些。
4.1 swapoff不是随便敲的
我已经提了好几次 swapoff 的风险,这里展开讲透。
swapoff 的本质是把 swap 空间中的所有内容搬回物理内存。如果当前可用内存不够容纳这些数据,内核就会很痛苦:一边要腾内存,一边还要等 swap 里的数据搬回来,整个系统会拖到近乎卡死。更极端的情况下,内核会启动 OOM killer 杀掉进程来强制回收内存。
判断是否适合 swapoff,我常用的标准是:available 内存 > SwapUsed × 1.5 以上,且当前没有明显的内存增长趋势。比如 SwapUsed 只有 200M,available 有 6G,那 swapoff 毫无压力。反之 SwapUsed 有 15G,available 只有 4G,那你千万不要敲这个命令。
如果必须 swapoff 但内存又不够,有两个缓解手段:
第一,先清理页缓存。sync && echo 3 > /proc/sys/vm/drop_caches能释放很大一部分缓存内存,腾出空间给 swapoff 用。
第二,用“汤换水”的方式,先启用新的更大的 swap,把旧 swap 的优先级调低,让系统优先使用新空间。等旧 swap 里的内容被慢慢替换掉,used 降到很低之后,再 swoff 旧的。步骤大概是这样:
# 1. 新 swap 已启用,把新 swap 优先级调高 swapon -p 100 /swapfile1 # 2. 把旧 swap 优先级调低(需要先关闭再重新挂载,或者用 swapon --priority 重新挂?实际做法:先swapoff旧的再swapon -p -1 挂回旧,但这样还是有一次swapoff)严格来说,这个“替换”过程必然要经历至少一次 swapoff。但区别在于:你是在新空间已经顶上、系统压力缓解后才去动旧的,而不是在内存告急时硬来。这个顺序不能搞反。
4.2 优先级、swappiness和fstab的隐藏坑
优先级是个很实用的参数。当系统里有多个 swap 设备时,内核按优先级从高到低使用。默认情况下所有 swap 设备的优先级是负值(通常是 -2),系统基本是平均使用。你可以手动设置:
swapon -p 100 /swapfile1优先级范围是 0 到 32767,数字越大越优先。扩容场景的用法就是给新的大 swap 一个高优先级,让它先被填满,旧 swap 哪怕不删,也只是个兜底。这个思路在线扩容时特别好用:你不需要急着清理旧空间。
swappiness 也顺便说一下。这个参数控制内核把内存页换到 swap 的激进程度,范围 0-100,默认通常是 60。服务器上我一般调成 10 左右,减少不必要的 swap 写入:
sysctl vm.swappiness=10 echo 'vm.swappiness=10' >> /etc/sysctl.conf设置完不用重启,立即生效。
fstab 的坑前面已经说了两个(UUID 变化、nofail 选项),还有一个很多人在意的:swap 文件的 fstab 行如果写错,开机时会卡 90 秒甚至更久,卡在a start job is running for dev-disk-by...swap。解决方案除了 nofail,还可以在写 fstab 之前用findmnt或swapon -a提前验证。改完配置后重启前,一定先执行:
swapoff -a swapon -a能顺利执行完,fstab 基本就没问题。
5. 现场问题排查速查表
把这些年遇到的 swap 扩容报错整理成一张速查表,基本覆盖了 90% 的翻车现场。
5.1 高频报错及原因分析
| 报错信息 | 原因 | 解决办法 |
|---|---|---|
swapon failed: Invalid argument | swap 文件所在的文件系统不支持(NFS、tmpfs 等)或使用了 fallocate 导致的 holes | 换 dd 重新创建,或改用本地文件系统 |
swapon: /swapfile: read swap header failed | mkswap 没有成功执行 | 先执行 mkswap 再 swapon |
swapfile has holes | fallocate 创建的文件在部分文件系统上不连续 | 删除文件,改用 dd 重新创建 |
Permission denied | swap 文件权限过大,普通用户可读 | chmod 600 |
| 开机卡在 swap 等待 | fstab 中 swap 条目异常 | 进入紧急模式修正 fstab,或加 nofail |
lvextend后 free 看不到变化 | 逻辑卷扩展了但 swap 区域没有重建 | swapoff 后 mkswap 再 swapon |
mkswap报Device is busy | 该设备正在被内核用作 swap | 先 swapoff 再执行 |
这里我特别想强调的是第一行。NFS 上的文件做 swap 是直接不行的,swapon会明确报Invalid argument。之前有人问我能不能把 swap 放在云盘的 NFS 挂载目录下,我说别想了,swap 必须是本地块设备或本地文件系统上的文件。
5.2 几个生产环境里的冷门经验
第一,查询哪个进程在大量占用 swap,有一个快速办法:
for f in /proc/*/status; do awk '/VmSwap/{sw=$2} /Name/{n=$2} END{print sw, n}' $f; done | sort -rn | head -10这条命令能列出占用 swap 最多的前 10 个进程。扩容之前看一眼这个,往往能发现有进程内存泄漏,光扩 swap 治标不治本。
第二,关于 swap 大小的老生常谈要纠正一下。“物理内存的 2 倍”这种说法是当年内存只有 512M 时代的经验,放到现在动辄 64G、128G 内存的服务器上完全不适用。我的习惯是:纯计算型服务器如果内存充足,swap 给 4G 到 8G 兜底就够;内存小(比如 4G 以下)的机器,swap 做成内存的 1 到 2 倍更稳。swap 不是越大越好,因为 swap 使用的磁盘写在性能和寿命上都有代价。
第三,云盘在线扩容有了新空间之后,如果你打算新建 swap 文件,记得先用df -h确认目标挂载点空间足够。很多人习惯在根目录/下建文件,结果根分区空间只剩 2G,还想建 8G 的 swap,大概率创建失败。遇到这种情况,把 swap 文件放到空间更大的数据盘上去,比如/data/swapfile,完全没问题,只要那个挂载点支持本地文件系统。
第四,容器环境里对 swap 的操作有个特殊点:如果你在容器里执行swapon --show看到的是宿主机信息,说明容器没权限管理自己的 swap。这种场景下不要白费力气,swap 配置应该在宿主机层面处理。
写在最后的体会
做过太多次 swap 扩容之后,我现在最深刻的感受是:扩容本身不复杂,复杂的是别把自己搞挂。在线扩容的思路总结下来其实就三条——优先新增代替修改、swapoff 前必须评估内存余量、fstab 配置永远加 nofail。这三条做到,基本不会翻车。另外我个人有个习惯,每次扩容完会顺手在/etc/sysctl.conf里检查一下 swappiness,避免系统没事儿就把内存往 swap 里倒。如果你现在的服务器 swap 已经在报警边缘,不妨按这篇文章的决策顺序来:先看形态,再选方案,动 swapoff 前三思,扩容完成立刻验证。整个过程冷静下来,十五分钟就能搞定。