
本文教你如何理解并调优 Linux 内核的 swappiness 参数与 OOM(Out Of Memory)Killer 行为,覆盖原理、典型场景与排查路径。读完你会得到三件产出:按数据库、容器、桌面三类负载的 swappiness 取值建议;针对 OOM 事件的根因定位流程;针对关键进程做 OOM 豁免与优先级调整的实战方法。
一、swappiness 不是把内存换到磁盘的开关
很多文档把 swappiness 描述成”swap 倾向程度”,0 表示永不 swap、100 表示积极 swap,这是常见误解。它实际是内核回收匿名内存(anonymous,进程堆栈这类没有文件对应的内存)与回收文件缓存(page cache)的相对权重。swappiness=60 时大致意味着同等水位下匿名内存 60、文件缓存 140 的回收倾向比例。
为什么这点关键?因为 swappiness=0 并不会让系统拒绝 swap,只是在内存紧张时优先回收 page cache。如果 page cache 都被回收光了内存仍不够,照样会动用 swap。同样地 swappiness=100 也不会让正常运行的系统提前 swap,只是在压力上来时更积极。云服务器(基于虚拟化的弹性计算资源)通常会按官方镜像给一个默认值(多数 60),相关选型可参考 云服务器选购指南。
二、不同场景下的取值建议
按负载类型给出实战建议:
- 数据库主机(MySQL、PostgreSQL、Redis):1 或 10,目标是几乎不 swap,让 buffer pool 与 page cache 充分利用
- 通用 Web 应用服务器:10-30,留出小量 swap 缓冲突发,但不积极换出
- 容器主机与 Kubernetes 节点:10,配合
vm.overcommit_memory=1与节点上 swap 关闭使用 - 桌面工作站:60-100,多任务下让内核积极回收以避免 OOM 弹窗
- 备份与批处理机:30-60,I/O 密集且内存压力波动大,适度 swap 反而稳
设置方法:临时 sysctl -w vm.swappiness=10,持久化写 /etc/sysctl.d/99-memory.conf。验证用 sysctl vm.swappiness 或 cat /proc/sys/vm/swappiness。配合 MySQL 部署的内存管理可参考 美国 VPS 部署教程 中的实例规划章节。
三、OOM Killer 的打分与选择机制
当系统内存与 swap 都耗尽时,OOM Killer 启动并杀掉一个或多个进程释放内存。它的选择依据是 oom_score,由内核根据进程 RSS(Resident Set Size,物理内存占用)、运行时长、子进程数、用户调整值(oom_score_adj)综合算出。分数越高越容易被杀。
如何看进程的当前分数?cat /proc/<pid>/oom_score 直接读。要把关键进程从 OOM 列表里”挡一挡”,写 oom_score_adj:
echo -1000 > /proc/$(pidof mysqld)/oom_score_adj
取值范围 -1000 到 1000,-1000 等于绝对豁免(早期内核叫 oom_disable),1000 等于优先送死。systemd unit 文件用 OOMScoreAdjust=-900 设定。注意把守护进程豁免后,OOM 发生时会去找其他进程,可能把无辜的辅助服务杀掉,所以豁免只给真正关键的核心服务。
四、cgroup v2 下的 memory.oom.group 与限额
cgroup v2 给容器/服务级别的 OOM 控制带来质变。memory.max 设硬上限,超过就触发 cgroup 内的 OOM;memory.high 设软上限,超过会触发回收而非杀进程;memory.oom.group 设为 1 时,OOM 会杀掉整个 cgroup 内所有进程而非只杀一个。
容器场景下这套组合非常实用:把每个服务放进独立 cgroup,memory.max 按需求 +20% 设定,memory.oom.group=1 保证子进程死则全死,便于守护进程拉起整体重启。CDN(Content Delivery Network,内容分发网络)边缘节点的多服务共存机器也用得上,相关边缘缓存与压缩策略可继续看 W3 Total Cache 调优实战 与 WordPress 香港主机选购指南。
五、定位 OOM 事件的根因
OOM 发生后 dmesg 与 /var/log/messages 会留下一段完整的内存快照与候选打分列表。关键信息看四点:
- “Out of memory: Killed process xxx” 这一行的进程名与 RSS
- “oom-kill:” 行里 cgroup 路径,确定是全局 OOM 还是 cgroup OOM
- “Mem-Info:” 后面的 free、available、buffer/cache、swap free 各项
- 整段进程列表里 RSS 最高的几个,是否是预期的
journalctl -k –since “1 hour ago” 也能查内核日志,时间过滤更方便。定位到肇事进程后再决定方案:内存泄漏修代码、配置过大改配置、机器配置不够升内存、关键进程加 OOM 豁免。事故复盘建议把每次 OOM 都登记到内部 ticket 系统,长期看就能发现哪些组件是常客。更多服务器维护可继续阅读 WordPress 分类下的相关文章。
六、要不要在生产开 swap
老派观点是”加大 swap 等于多一份保险”,现代观点更倾向”关键服务器禁 swap、容器节点必须禁 swap”。理由是 swap 一旦被用,业务延迟瞬间几十到几百毫秒,监控告警还不一定准。Kubernetes 直到 1.22 才正式支持节点开 swap,且默认仍要求关闭。
折中方案:保留一个 4-8GB 的 swap 文件,swappiness 设为 1 或 10,作为 OOM 前的最后一道缓冲;同时配上磁盘空间告警,避免 swap 把磁盘塞满引发新问题。数据库主机一般直接 swapoff -a 并禁用 swap fstab 条目,靠 buffer pool 与 page cache 自己管理就够了。
把 swap 与 zswap、zram 也对比一下:zswap 是内核内置的压缩 swap 缓存,能把 4GB swap 等效放大到 6-8GB,对内存紧张但又不想加磁盘 swap 的中等机器友好;zram 则是把一段内存当压缩块设备直接用作 swap,纯内存方案,桌面与嵌入式常用。
七、监控与告警对接
OOM 是低频但高危事件,必须有告警。三种主流接法:
- node-exporter 抓
node_vmstat_oom_kill增量计数,超阈值告警 - journald 配合 vector/Promtail 把 “Out of memory” 关键字推到告警系统
- systemd 的 OnFailure= 指令在关键服务挂掉时触发邮件或机器人通知
告警内容除了”哪个进程被杀”之外,建议同时附上事发时的内存快照、top 进程列表、cgroup 占用,便于复盘。每次 OOM 都登记到 ticket 系统,按月统计常客组件,针对性扩内存或修代码。这套机制坚持下来,OOM 引发的故障会明显减少。
监控之外,对内存使用做长期趋势分析也很有价值:突然出现的内存爬升往往是缓存类组件的回收策略变了,或者某个版本上线引入了内存泄漏。
总结与建议
swappiness 与 OOM Killer 是内存压力下两个相互配合的旋钮:前者控制回收倾向,后者控制最后一道防线。建议你按本文顺序落地:先按业务类型选 swappiness 值、再给关键进程加 oom_score_adj 或 systemd OOMScoreAdjust、最后用 cgroup v2 做服务级隔离。如果你需要在多机部署,可以考虑把这套 sysctl 与 systemd drop-in 做成 Ansible 角色统一下发,并把 OOM 事件接到 Prometheus alertmanager。总结一下,内存调优的目的不是消灭 OOM,而是让 OOM 发生时影响面可控、定位路径清晰、恢复时间可预期,这才是稳态。