订阅更新

vmstat 一敲 si/so 一直在涨,机器慢得像 286——swap 触发的完整排查

Linux手册 第 17 题 约 11 分钟读完

本文目录
  1. 先说结论
  2. swap 到底什么时候被触发
  3. 排查 swap 问题的三板斧
  4. 场景还原:MySQL 半夜被 JVM 吃穿的那台机器
  5. 修复 & 防复发
  6. swap 配置建议
  7. 面试高频追问
  8. 总结

先说结论

第一条: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 -a1延迟敏感,绝不能命中磁盘
JVM 应用(Flink/Spark)少量保留 2-4G10防 OOM 兜底,但平时不用
Kafka broker关或极少1页缓存为王,swap 会干扰
批处理 Hadoop1-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年大数据老兵,聊技术、聊职场、聊成长

刷题进度保存在当前浏览器