一、现象与影响面:加了 swap,卡顿没走
症状:小内存服务器(1-2G)周期性卡顿几十秒又自己恢复,SSH 敲命令都慢半拍;或者你已经加了 swap,”该好一点了”却没有——卡顿照旧,只是换了个姿势。内存压力的卡是全场一起卡:接口超时、数据库慢、命令行卡顿同时出现,这与单进程吃 CPU 的问题形态完全不同。
动手之前先立一个判断原则:swap 是缓冲垫,不是解药。它只在”内存偶发超出”时替你争取时间;内存长期不够时,swap 只是把”内存不够”翻译成”磁盘 IO 不够”——磁盘比内存慢两个数量级,卡顿会更久而不是更短。反模式一:一卡就加 swap。如果换页已经持续活跃,加 swap 等于把性能问题降级成一个更慢的问题。先取证,再决定加不加。
排查主线:free 看水位与趋势 → vmstat 的 si/so 判定是不是真抖动 → 找出谁躺在 swap 里 → 核对 swappiness 的真实语义 → 按决策表选”加 swap”还是”治本”。所有命令都在卡的那台服务器上跑。
二、第一步:free 看的不是 free,是 available
很多人被 free 输出的第一列骗了十年。看懂每列语义是判断内存压力的前提:
free -h
1. total used free shared buff/cache available
1. Mem: 1.8Gi 1.5Gi 180Mi 24Mi 120Mi 210Mi
1. Swap: 2.0Gi 640Mi 1.4Gi
1. ↑ free 少不等于内存紧张:buff/cache 是内核拿去缓存的闲置内存,
1. 应用要用时立刻让出。判断水位只看 available——"不换页就能挤出来"的估算
单点数值不够,挂个 watch 看趋势,一分钟就够定性:
watch -n 1 free -h # 每秒刷新,盯一至两分钟
1. 两种典型曲线:
1. available 在某个区间内波动,偶有下探但总回弹 ← 峰值代谢,健康
1. available 单调下探、长期不复返 ← 有进程在持续吃,压力为真
反模式二:把 free 数值低当故障报修。free 少而 available 充足,是内核缓存正常工作的样子,不是泄漏。这一步回答”内存压力是真是假”;压力为真,下一步看 swap 到底在帮谁、坑谁。
三、第二步:vmstat 的 si/so,判定”真抖动”还是”偶发换页”
内存压力与 swap 的关系,用 vmstat 每秒采样最直接:
vmstat 1 10
1. procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
1. r b swpd free buff cache si so bi bo in cs us sy id wa
1. 1 0 65536 180000 24000 120000 4 8 120 90 210 380 12 4 74 10
1. 2 1 131072 120000 24000 118000 240 380 140 4200 380 620 15 6 21 58
1. 2 1 196608 86000 22000 116000 260 410 110 5100 420 700 14 5 12 69
1. ↑ si/so 每行都有几百的数、wa 一路走高——换页风暴,磁盘在替内存扛
两列是关键:si(swap in)与 so(swap out),每秒换入换出的 KB 数。判定标准不是”非零”,而是持续性:
| si/so 表现 | 判定 | 下一步 |
|---|---|---|
| 持续非零(每行都有数百以上)且 wa 高 | 换页风暴,内存真不够,卡顿正来自换页 IO | 第四节找出元凶,第六节治理 |
| 偶发几行非零随即归零 | 峰值过境的正常代谢,swap 在正确兜底 | 不用动,接入监控即可 |
| 全程为 0 但机器仍卡 | 瓶颈不在内存,别在 swap 上耗着 | 转 CPU、磁盘 IO 等其他方向 |
卡顿时刻 si/so 持续活跃,就完成了第一层实锤:这不是”加了 swap 就好”的问题,换页本身正在制造卡顿。现在找是谁在换。
四、第三步:找出谁躺在 swap 里
内核按进程记账换页,每个进程在 /proc 下都有 VmSwap 字段。一个循环就能排出”换页大户”:
# 服务器上(root);按换出量从大到小列前几名
swapon --show
1. NAME TYPE SIZE USED PRIO
1. /swapfile file 2G 640M -2 ← 有哪些 swap、各用掉多少
for p in /proc/[0-9]*/; do
s=$(awk '/VmSwap/{print $2}' "$p/status" 2>/dev/null)
[ "${s:-0}" -gt 0 ] && printf '%10d KB %s\n' "$s" "$(cat "$p/comm" 2>/dev/null)"
done | sort -rn | head
1. 512000 KB mysqld
1. 86000 KB java
1. 12000 KB myapp-worker
1. ← 延迟敏感的进程躺在 swap 里,就是卡顿元凶
看排名要带业务眼光:数据库、Redis 这类进程换出的每一页,命中时都要从磁盘读回来,一次查询的耗时能翻几十倍;Java、Go 这类运行时被换出后,GC 与调度会引发连锁换页。反过来说,换出的是偶尔才访问的后台进程,那属于 swap 的正常用法。排名把”机器内存不够”细化成”哪个进程该限内存或该挪走”——带着这份名单进治理决策。
五、第四层:swappiness 的语义正名
提到 swap 必谈 vm.swappiness,但围绕它的误解多得离谱。先正名:它是内核在”回收 page cache”与”把匿名页换出到 swap”之间的倾向权重,取值 0-100,默认 60(长期稳定默认,具体语义以所用内核的 ip-sysctl 文档为准):
| 常见误解 | 实际语义 |
|---|---|
| “60 表示用到 60% 内存才开始 swap” | 它不是水位阈值,是两种回收路径之间的权重倾向 |
| “设 0 就是禁用 swap” | 不是。0 只表示尽量避免换出匿名页,真要禁用请 swapoff |
| “设 100 内存随便换,肯定不卡” | 只是更积极换出,不是开关;数值越大热数据越容易被换走 |
# 服务器上;临时改用 sysctl,永久生效写入 sysctl.d
sysctl vm.swappiness
1. vm.swappiness = 60
sysctl -w vm.swappiness=10
echo "vm.swappiness=10" > /etc/sysctl.d/99-swappiness.conf # 重启后仍生效
实践口径:数据库、缓存这类延迟敏感服务所在的主机调低(10 左右),让内核优先回收缓存而不是把热数据换出;通用业务机保持默认即可。诚实边界:swappiness 是调倾向,不是调容量——si/so 持续活跃时把它调小只能减轻症状,不能止血,止血靠下一节的决策。
六、治理决策:什么时候加 swap,什么时候治本
所有取证都指向同一张决策表:
| 症状 | 决策 | 理由 |
|---|---|---|
| 偶发峰值击穿 available(备份、编译、月结批任务) | 加 swap 兜底 | 换出的是冷页,代价小,还能让内核晚一点动用 OOM 杀手 |
| si/so 持续活跃、wa 居高不下 | 治本:给泄漏进程限内存、加内存、或把服务挪走 | 持续赤字 swap 挡不住,只会把卡顿拉得更长 |
| VmSwap 排名集中在单个进程 | 治理那个进程,而不是动整机 | 容器场景给该容器设 memory 限额;自研服务先查泄漏 |
| 裸机没配 swap,偶发峰值直接丢进程 | 补一个小 swapfile | 内存不足时内核 OOM killer 直接杀进程,swap 是它之前的最后缓冲 |
决定加 swap,用文件方式最灵活(ext4 上一步到位;btrfs 等文件系统对 swapfile 另有限制,以内核与发行版文档为准):
fallocate -l 2G /swapfile # 开一块 2G 的文件
chmod 600 /swapfile # 权限必须收紧,不设 swapon 会直接拒绝
mkswap /swapfile # 格式化成 swap
swapon /swapfile # 立即启用,swapon --show 可验
echo '/swapfile none swap sw 0 0' >> /etc/fstab # 写进 fstab,重启仍生效
swap 多大合适?诚实地说没有万能公式。”内存两倍”是上个年代小内存机器的经验,8G 内存的服务器照抄只会白占盘。原则是小而稳:兜底用 1-4G,具体大小由第二步观察到的峰值缺口决定;桌面机要休眠的另算,休眠要求 swap 不小于物理内存。至于治本清单:给名单上的进程设内存限额、查代码泄漏(内存泄漏的定位是另一类独立故障,本文不展开)、单机纵向到顶就考虑把服务横向分担。
七、根因清单:按出现频率排序
| 根因 | 典型特征 | 一行验证 |
|---|---|---|
| 泄漏型进程持续吃内存 | available 单调下探不复返 | watch -n 1 free -h 盯两分钟 |
| 批处理/备份撞峰 | 卡顿时刻与计划任务时刻吻合 | 卡顿时刻表对照 cron 计划 |
| 数据库/缓存热数据被换出 | 慢查询与卡顿同时爆发,重启后短暂好转 | VmSwap 排名看 mysqld/redis |
| 小内存机 swappiness 默认偏高 | available 尚有余量却频繁 so | sysctl vm.swappiness |
| 没配 swap 的裸机遇峰值被 OOM | 进程莫名消失,dmesg 有杀进程记录 | dmesg -T | grep -i oom |
| buff/cache 被误读为泄漏 | “used 一直涨”实为缓存吃满 | free -h 只看 available 列 |
八、预防
| 措施 | 做什么 | 边界 |
|---|---|---|
| available 水位告警 | 低于 10-15% 告警,采 available 而不是 free | 阈值按业务峰值留量,别照抄别人的数 |
| 换页持续性告警 | si/so 连续 N 分钟非零即告警 | 比”swap 用量”更早发现问题——用量涨起来时卡顿已经开始 |
| VmSwap TOP 采集 | 巡检带上第四节的一行排名 | 只采前几名,全量遍历在进程多的机器上偏慢 |
| 装机基线 | 装机清单含 swapfile 与 swappiness 基线值 | 数据库主机与通用主机基线不同,分开写 |
这套走法收拢成一句:available 的趋势判定内存压力真假,si/so 的持续性判定 swap 是在兜底还是在遭殃,VmSwap 排名指认元凶——偶发峰值交给 swap 缓冲,持续赤字只有治本一条路,swappiness 从头到尾都只是倾向调节,不是容量。
延展阅读:free、vmstat、dmesg 这些观测命令的日常用法,见《Linux 常用命令》;单机内存纵向加到头还扛不住时,下一步是把服务横向分担,见《多节点集群架构规划》。
版本核实(以官方文档为准):free 的 available 列语义、vmstat 的 si/so/wa 列含义、vm.swappiness 默认 60 且为回收倾向权重、swapon 对 swapfile 权限的强制检查,均为长期稳定的内核与工具行为;fallocate 创建 swapfile 在 btrfs 等文件系统上有额外限制,以内核与发行版文档为准;OOM killer 的触发与定位属另一类故障,本篇只作现象衔接,不展开其机制细节。