vmstat 一敲 si/so 一直在涨,机器慢得像 286——swap 触发的完整排查
先说结论
第一条:swap 被触发不一定是「free 没了」——Linux 的内存分配看的是 available + swappiness 的综合策略,即使有 free 也可能主动 swap out 冷页。
第二条:排 swap 问题第一条命令永远是 vmstat 1 看 si/so,第二条是 cat /proc/pressure/memory 看 PSI,第三条是 smem -r 看哪个进程吃得最多。
第三条:数据库、缓存、JVM 这类对延迟敏感的进程所在的机器,强烈建议 swap 关掉或把 vm.swappiness=1;吞吐型作业可以保留 swap 作为缓冲。
先把这段讲顺;面试官追问时,再展开下面的原理和细节。
详细讲解
某出海硬件品牌一台 MySQL 从库半夜开始慢,查询从几十毫秒涨到几秒。运维上去 top 一看内存还有 2G free,但 vmstat 1 一敲——si 7000、so 5000,一直在涨,系统已经在疯狂换页。用户态看起来「内存还够」,内核态已经在拿磁盘当内存用了,机器慢得像 286。
这是一道面试里能迅速区分「只看 top」和「真懂内存」的题。很多候选人能说出 swap 是什么,但说不清「机器还有 2G free 为什么会 swap」「swappiness 到底是个什么东西」「生产该不该彻底关 swap」。这些细节一问一个准。
swap 到底什么时候被触发
很多人以为 swap 只在「内存不够」时才触发,这是错的。真实的规则比这复杂得多:
# vm.swappiness 取值 0-100,默认 60
cat /proc/sys/vm/swappiness
# 60
# swappiness 的含义不是简单的 '剩多少才 swap'
# 内核公式(kernel/mm/vmscan.c):
# file_prio = 200 - swappiness -- 文件缓存回收优先级
# anon_prio = swappiness -- 匿名页(程序内存)回收优先级
#
# swappiness=60 意味着:内核权衡时 100 份压力里 60 份推给匿名页(swap)
# 40 份推给文件缓存(drop cache)
# swappiness=10:极力避免 swap,先 drop file cache
# swappiness=0:完全禁用匿名页换出(但紧急时仍可能 swap,Linux 3.5+ 行为)
# swappiness=1:保留 1% 倾向,可让内核不至于在内存紧张时完全无策略可选
# 另一个很多人不知道的参数:
cat /proc/sys/vm/vfs_cache_pressure
# 100(默认)= 回收 dentry/inode 缓存的倾向,和 swappiness 配合工作
关键误区:很多人以为 swappiness=0 就完全不 swap。Linux 3.5 之前确实如此,但 3.5+ 之后 swappiness=0 只是把 swap 权重调到最低,内存彻底不够时内核还是会 swap。真正想彻底避免 swap 要 swapoff -a 或 sysctl vm.swappiness=1 + echo never > /sys/kernel/mm/transparent_hugepage/enabled 配合用。
排查 swap 问题的三板斧
机器慢了怀疑 swap,按这三步走,90% 的问题都能定位:
# 第一板斧:vmstat 1 看 si/so 是否在涨
vmstat 1 10
# procs -----------memory----------- ---swap-- -----io----
# r b swpd free buff cache si so bi bo
# 0 1 8G 500M 100M 2G 7000 5000 8000 6000
# 0 2 8G 480M 100M 2G 7500 5200 8500 6500
# ↑ swpd=8G 说明已经有 8G 被换到磁盘
# ↑ si 7000 KB/s = 每秒从 swap 读回 7MB
# ↑ so 5000 KB/s = 每秒往 swap 写出 5MB
# ↑ 这是典型的 'swap thrashing'(换页抖动),机器几乎只在做换页,业务停摆
# 第二板斧:PSI 看内存压力(4.20+ 内核)
cat /proc/pressure/memory
# some avg10=45.22 avg60=40.18 avg300=35.50 total=1234567
# full avg10=25.10 avg60=22.05 avg300=18.30 total=789456
# avg10 > 20 就已经是明显内存压力
# full > 10 说明有时整个系统都卡在等内存,等于内存已经是瓶颈
# 第三板斧:按 swap 占用排序进程
smem -rs swap | head -20
# PID User Command Swap USS PSS RSS
# 12345 mysql /usr/sbin/mysqld ... 5.2G 15G 16G 18G
# ← 5.2G 被 swap 出去了,mysqld 是主凶
# 没装 smem 时的平替:
for pid in $(ps -eo pid --no-headers); do
swap=$(grep VmSwap /proc/$pid/status 2>/dev/null | awk '{print $2}')
[ -n "$swap" ] && [ $swap -gt 10240 ] && echo "$swap $pid $(ps -p $pid -o comm=)"
done | sort -rn | head
为什么 si/so 比 swpd 更关键:swpd 是累计值,哪怕机器已经不 swap 了,swpd 也不会自动归零——只有 swapoff 或进程重启才会清。si/so 是「当前每秒换入换出速率」,这个长期大于 0 才说明系统「正在」swap 抖动。面试官问「怎么判断机器正不正常地 swap」,你一定要说 si/so 持续大于 0,不是看 swpd 有没有值。
场景还原:MySQL 半夜被 JVM 吃穿的那台机器
某出海硬件品牌那台 MySQL 从库,凌晨 2 点开始慢。排查过程:
# 监控告警:MySQL 慢查询从 50 条/分钟涨到 5000 条/分钟
# ssh 上机
top
# MiB Mem: 32000 total, 1800 free, 28000 used, 2200 buff/cache
# MiB Swap: 16000 total, 8000 free, 8000 used
# ← Swap used=8G,说明已经换页到磁盘了
vmstat 1 5
# r b swpd free ... si so
# 0 3 8100M 1800M ... 5200 4800
# 0 4 8102M 1780M ... 5500 5000
# ← si/so 都在 5000+,典型换页抖动
# 谁在吃内存
smem -rs rss | head -5
# PID User Command Swap USS RSS
# 12345 mysql mysqld 5G 15G 18G
# 23456 tomcat java -Xmx12g ... 2G 11G 13G ← 意外元凶
# 为什么会有 tomcat?这台机器理论上只跑 mysql
ps -ef | grep tomcat
# 原来运维临时把一个监控采集器(java -Xmx12g)部署到了这台机器,
# -Xmx12g + MySQL innodb_buffer_pool_size=16g + 系统 4g = 32g 正好吃满
# 一旦有瞬时分配高峰,系统只能 swap
根因定位:MySQL innodb_buffer_pool 本身配置的 16G 没问题,坏就坏在临时部署的 tomcat 采集器吃了 12G,两者加一起远超物理内存,内核被迫 swap MySQL 的 buffer pool 冷页,导致查询大量命中磁盘而不是内存,延迟飙升。
修复 & 防复发
# 紧急修复:先把 tomcat 迁走
systemctl stop tomcat-collector
# swpd 不会自动归零,但 si/so 会迅速降到 0
# 彻底清掉 swap 里的内容(让 MySQL 冷页回到内存)
swapoff -a && swapon -a
# 注意:swapoff 会把 swap 里所有数据换回物理内存
# 如果物理内存不够,会 OOM,生产必须确认 free 足够再跑
# 长期方案 1:sysctl 调低 swappiness
echo 'vm.swappiness = 1' >> /etc/sysctl.d/99-custom.conf
sysctl -p /etc/sysctl.d/99-custom.conf
# 长期方案 2:MySQL 机器建议直接 memlock 锁内存
# my.cnf:
# innodb_buffer_pool_size = 16G
# memlock = ON # 需要 root 或 CAP_IPC_LOCK
# 这样 buffer pool 被 mlock 到内存,内核不能 swap 出去
# 长期方案 3:禁止混部
# 加 taint 或 systemd 资源限制,避免 tomcat 那种突然部署到数据库机器
防复发硬规定:MySQL/Redis/ES 这种延迟敏感的数据库类服务,机器不跟其他服务混部,swap 要么彻底关,要么 swappiness=1。一台机器一个主角,别让临时任务挤进来。
swap 配置建议
| 场景 | swap 配置 | swappiness | 原因 |
|---|---|---|---|
| MySQL/Redis/ES | 关 / swapoff -a | 1 | 延迟敏感,绝不能命中磁盘 |
| JVM 应用(Flink/Spark) | 少量保留 2-4G | 10 | 防 OOM 兜底,但平时不用 |
| Kafka broker | 关或极少 | 1 | 页缓存为王,swap 会干扰 |
| 批处理 Hadoop | 1-2 倍内存 | 60 | 吞吐型,swap 可作缓冲 |
| 开发 / 测试机 | 默认 | 60 | 不要求延迟 |
面试高频追问
追问一:swap 能彻底关掉吗?关了有什么风险?
可以,swapoff -a 然后 /etc/fstab 里注释掉 swap 那一行,重启依然不会挂载 swap。风险是:内存真的不够时,内核没有退路,直接触发 OOM killer 杀进程。所以关 swap 的前提是物理内存足够 + 有可靠的 OOM 策略(调好 oom_score_adj)+ 有内存监控告警。数据库机器我 100% 会关 swap,因为'慢'比'死'更难诊断——内存不够就让它 OOM,监控告警后马上扩容或排查,比在 swap 泥潭里挣扎强。
追问二:为什么 free 命令的 free 一栏那么少,但 available 一栏很大?
free 是严格意义上的「完全没人用的内存」,Linux 把文件 cache 算成 used,所以 free 往往很小。available 才是真正「可供新分配使用的内存」——它等于 free + 可回收的 page cache + 可回收的 slab。Linux 的设计哲学是「空闲内存就是浪费」,所以一个健康的 Linux 系统 free 值普遍很低,看 available 才有意义。判断内存是不是真紧张,永远看 available,不看 free。
追问三:容器里的 swap 和宿主机 swap 是什么关系?
容器默认和宿主机共用 swap——容器里看到的 /proc/meminfo 的 swap 字段其实是宿主机的(除非显式用 cgroup memory.swap.max 限制)。K8s 在 1.22 之前 kubelet 强制要求主机关 swap,原因就是 swap 会让内存限制失真:容器明明分配 4G 内存,实际可能用了 4G RSS + 2G swap,资源统计乱套。1.22+ 支持有限的 swap 控制,但生产建议还是宿主机关 swap,容器用 memory limit 硬性控制,OOM 时直接 kill 容器而不是拖死宿主机。
总结
swap 是 Linux 内存管理的一个「兜底方案」——内存真不够时给你一条退路,但退路本身是一个性能陷阱。面试时你能把 swappiness 的真实含义、si/so 的监控意义、数据库机器为什么要关 swap 这三点讲清楚,基本就是'有生产经验'的标志。最后一句话记住:延迟敏感的服务,swap 是毒;吞吐型批处理,swap 是糖。
下一篇预告:生产机 sysctl 到底调哪些——10 个必调内核参数清单。
你见过最诡异的 swap 抖动是什么原因造成的?评论区聊聊。
关注「Wood聊大数据」| 9年大数据老兵,聊技术、聊职场、聊成长