ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Linux swap 调优:vm.swappiness 参数实战与踩坑指南

Linux swap 调优:vm.swappiness 参数实战与踩坑指南 1. 为什么默认值 60 会成为系统性能的隐形瓶颈我先讲一个真实场景。去年我给一台 16G 内存的服务器做性能排查跑的是 Java 后端服务负载一直不高但响应时间隔三差五就飘一下。free -h 一看内存还剩下 7 个 Gswap 却偷偷用了 3 个 G。当时的第一反应是这不对劲。后来查 /proc/sys/vm/swappiness显示 60也就是 Linux 默认值。问题的根源就在这虽然物理内存还富余但内核认为 swap 是合理的回收目标把一部分不太活跃的匿名页比如 JVM 堆里很少被碰到的对象换到了磁盘上。等到程序真正需要这些数据的时候又要从磁盘读回来。一次 swap in、一次 swap out服务的响应时间立刻就能翻几倍。这就是 vm.swappiness 这个参数最坑的地方它不是内存不够才用 swap而是内存回收时倾向于把什么东西先换出去的调节旋钮。默认值 60 是很多年前针对通用场景定的放到今天的 SSD、大内存服务器、数据库实例、容器环境里往往不太合适。这篇文章我不会只告诉你改到 10 就好了而是把下面几件事讲清楚swappiness 到底在调什么、什么场景该调低、什么场景反而该调高、改完之后怎么能确认生效以及几个我在生产环境踩过的坑。最后一个都没踩过的参数不值得你花十分钟读但如果你正在被 swap 导致的性能抖动困扰这篇文章应该能帮你省下半天排查时间。2. swap 机制与 swappiness 的取值逻辑先把原理讲透2.1 内核在什么情况下会触发 swap把 swappiness 当作一个简单的百分比阈值来理解是很多人走偏的开始。它并不是说内存使用率超过 60% 就开始 swap而是和内核的内存回收page reclaim机制深度绑定。Linux 的内存管理把物理页分成两类文件页file-backed pages和匿名页anonymous pages。文件页是 mmap 映射的文件内容或者 page cache理论上可以随时从磁盘重新读出来干净页直接丢弃脏页写回磁盘即可。匿名页是进程自己申请的内存比如堆、栈、写时复制产生的 private 页没有磁盘上的对应文件换出只能进 swap 分区或 swap 文件。当系统内存压力升高时内核会通过 kswapd 后台回收线程和 direct reclaim直接回收两条路径去腾内存。这个压力是用扫描成本来衡量的比如每个内存 zone 的 watermarks一旦 free pages 掉到 low watermark 以下回收就会被激活。而 swappiness 参数控制的是在一次回收中匿名页和文件页的扫描比例倾向。它的取值范围是 0 到 200默认 60。数值越大越倾向于把匿名页换到 swap 来释放物理内存数值越小越倾向于回收文件页。网上很多人把它等同于使用 swap 的倾向严格说不准确应该说是回收匿名页的倾向。2.2 默认值 60 背后的历史权衡为什么 Linus 他们当年把默认值设成 60核心考虑是文件页回收成本低但 page cache 对读写性能很重要匿名页换出成本高要先写磁盘但换出之后那部分物理内存可以立刻给 page cache 用提升文件访问命中率。60 这个值相当于适度偏向匿名页换出适合当年那种内存不大、磁盘还是机械硬盘、工作负载以常规 Web 服务为主的场景。在那个时代应用程序对内存的需求不像今天这么夸张JVM 动辄十几 G 堆、数据库 buffer pool 占满内存、容器多租户叠加都是后来才普遍出现的负载形态。我自己的理解是60 这个默认值更照顾通用桌面/服务器混合场景而不是某个具体的性能敏感应用场景。所以生产环境里几乎每个运维团队都会根据自己的负载特征去调它完全用默认值跑关键业务反而不太常见。2.3 swappiness 与 cgroup 内存限制的关系这块特别容易遗漏。如果你在用 systemd 跑服务或者容器环境里设置了 memory.limit_in_bytes那么 cgroup 层面的内存回收有自己的逻辑而且和全局的 swappiness 不完全一样。cgroup 的 memory.swappiness 默认继承全局值但可以单独覆盖。容器平台比如 Kubernetes 配合 kubelet 设置经常会显式配置 swappiness因为容器场景下我们通常希望尽量少 swap最好直接让 OOM Killer 去处理超限进程而不是让容器内进程卡在 swap 上。所以调优的时候要分清楚你改的是全局 /proc/sys/vm/swappiness还是某个 cgroup 的 memory.swappiness。如果服务跑在容器里只是改宿主机的全局值可能完全不生效。这个细节我后面在实操部分还会再展开。3. 什么样的场景需要调低什么样的场景可以调高3.1 数据库类负载调低基本是共识如果你跑的是 MySQL、PostgreSQL、Oracle 这类数据库swappiness 调低几乎是 DBA 圈子里的标准动作。原因很简单数据库的内存池比如 MySQL 的 innodb_buffer_pool、PostgreSQL 的 shared_buffers是精心设计出来缓存热数据的让内核把属于内存池的匿名页换出到磁盘等于把数据库的命中率白白扔掉然后再从磁盘读回来两头挨打。我见过不少生产事故表象是数据库突然慢查询变多排查到最后才发现是 kswapd 一直在把 buffer pool 的页换到 swap 上。解决办法就是把 swappiness 调到 10 甚至 1同时保证 innodb_buffer_pool 大小和实际数据热集匹配让内存池成为真正的驻留内存。这里给一个大致参考区间不是硬性标准但可以作为一个起点数据库实例1 ~ 10部分极端场景 0但要注意后文提到的风险Java 后端服务10 ~ 30取决于 JVM 堆设得多大通用 Web 服务器10 ~ 30桌面环境内存偏小80 ~ 100 甚至更高让内核更积极换出冷数据表 3-1 swappiness 经验参考值场景建议值说明MySQL/PostgreSQL1 ~ 10保护 buffer pool 驻留Java 中间件服务10 ~ 30平衡堆外内存与 page cache通用 Nginx PHP10 ~ 30保留更多 page cache 给静态文件图形桌面小内存80 ~ 100让不常用应用先出去保证交互流畅内存型缓存节点1 ~ 10避免 Redis 等内存数据落盘3.2 大内存服务器swap 基数小交互反而频繁还有一种常见误区机器内存 64G 或 128G觉得 swap 反正用不到swappiness 多少无所谓。实际上恰恰相反内存越大的机器你越要仔细设置。为什么因为 swap 空间分区或文件通常是固定大小的比如 8G 或者 16G。当系统内存使用率没那么高但内核因为 swappiness60 把 3G 匿名页换到 8G 的 swap 里时swap 的占用比例并不低。一旦业务流量上来内存需求加剧内核发现 swap 不够用了就会频繁地在匿名页和文件页之间来回倒腾造成持续的抖动。这种抖动比内存完全不够然后 OOM还难受因为进程不会挂但性能会很差而且用 top 看 CPU 使用率还不高只是 iowait 上涨。很多人遇到这种诡异卡顿时根本不会往 swappiness 上想因为内存明明还很充裕。所以大内存机器我的建议是如果确认工作集能放进物理内存直接设 1必要时配合 tmpfs 来做缓存尽力避免 swap 被写入。内存冗余本来就是用来扛波峰的留着给内核做 page cache 也比换出到磁盘有用。3.3 哪些场景反而可以调高 swappinessswappiness 不是越小越好。调高也有适用场景最典型的就是小内存桌面环境。举个例子一台 4G 内存的老笔记本平时开着浏览器十几个标签页、一个 IDE、几个终端。这种情况下工作集明显超过物理内存硬扛内存必然导致频繁地直接回收应用切换时卡顿明显。这时候把 swappiness 调到 80 或 100让内核更果断地把后台应用的匿名页换出反而能保证前台交互响应。缺点是 SSD 会多承担一些写入寿命不过现在 SSD 寿命基本不是瓶颈换来的是系统流畅度值。还有一种场景冷热数据区分度极高的服务。比如一个服务有很多长时间不访问的会话对象但不清楚具体哪些内核的 LRU 算法显然比应用层更清楚这时让 swappiness 保持中高值反而能让冷页尽快腾出物理内存给热数据。只是这种情况在业务代码里比较少见通常应用层可通过缓存淘汰解决不建议依赖内核替你判断。3.4 判断自己的系统属于哪种类型怎么判断最直接的方式是查 swap 使用量和内存压力使用free -h看 swap 列如果内存还有大量剩余但 swap 已经被使用说明 swappiness 在你的工作负载下偏高了。看/proc/pressure/memory里的 PSIPressure Stall Information指标如果 some 或 full 的压力值长期偏高说明内存回收确实在拖累系统光调 swappiness 不一定够可能还要配合应用内存优化。查询vmstat 5观察 siswap in和 soswap out这两个值长期非零基本可以断定 swap 正在频繁参与内存回收而这种情况下绝大多数业务都有可以优化的空间。我自己常用的套路是先加监控si/so、pswpin/pswpout再一次性把 swappiness 调到一个激进值观察业务指标 24 小时如果没问题再稳定下来。不要调到一半就不管了后面我会讲怎么验证。4. 动手设置临时调整、永久生效与常见坑位4.1 临时调节先测试再落地临时修改非常简单两种方式等价echo 10 /proc/sys/vm/swappiness sysctl -w vm.swappiness10实测下来sysctl -w用起来更顺手因为可以顺便确认返回值。临时修改的好处是重启即恢复默认值适合做对比测试。内核几乎即时生效不需要重启任何服务这一点很方便。确认生效sysctl vm.swappiness cat /proc/sys/vm/swappiness只要注意不要同时开多个终端去改避免命令重复覆盖导致自己都不确定当前值是什么。我就干过这种蠢事两边同时改最后还得靠 cat 来确定。4.2 永久生效的两种写法要持久化常规做法是写入 sysctl 配置文件echo vm.swappiness10 /etc/sysctl.conf sysctl -p但这里我要多说一句现代 systemd 系统上我更推荐在/etc/sysctl.d/下新建一个专门的配置文件比如99-custom-swappiness.conf。原因是/etc/sysctl.conf在有些发行版上只是兼容层其他软件可能也会写入同一个文件而且用单独文件可以很方便地通过文件名判断这个配置属于哪类优化。对团队协作来说也更容易 review 和回滚。cat /etc/sysctl.d/99-swappiness.conf EOF vm.swappiness10 EOF sysctl --systemsysctl --system会按/etc/sysctl.d/*.conf字典序依次加载同名的 key 后加载的覆盖先加载的。用99-前缀可以保证优先级最高避免被发行版自带的配置覆盖。4.3 systemd 服务和 cgroup 层面的覆盖问题如果说全局 swappiness 是第一条防线那么 systemd 服务级别的覆盖就是第二条容器环境则是第三条。在 systemd 服务单元里可以通过MemorySwapMax直接限制服务可用的 swap 量。更细致的做法是给执行工具systemd-run加参数systemd-run --scope -p MemorySwapMax0 --user your-command这在测试某个进程完全不允许 swap时有奇效。但要注意 systemd 的这个参数受 cgroup v2 支持老内核或 cgroup v1 环境下表现不完全一致。容器场景是重灾区。Kubernetes 默认行为在memory.swappiness上各版本差异很大很多平台甚至直接在 kubelet 层面禁用 swap。如果你在容器里改全局 sysctl大概率没戏得看平台是否允许设置 pod 级别的sysctls或者直接依赖镜像内的sysctl工具在容器启动时临时设置通常需要 privileged 权限。所以容器里调优 swappiness本质上是在问平台允不允许我碰内核参数而不是这个值该填多少。4.4 关于 swappiness0 的红线网上很多教程教人直接设 0我建议大家谨慎。内核文档里明确说过swappiness0 并非永远不 swap而是极端情况下仍然可能换出匿名页而且 0 值在某些内存压力出现时会带来更激进的文件页回收进而影响 page cache 命中率。说实话我在生产环境倒是见过设 0 跑得很稳的案例但那是配合了大页表和详细监控才敢上的。如果你刚开始接触这个参数从 10 起步比较稳让内核保留一丁点匿名页换出的空间作为极端情况下的安全阀。我还想额外提醒一个环境差异不同发行版给 vm.swappiness 设置的值可能不同。比如某些桌面发行版会把默认值改成 10 甚至更低如果你是从网上复制教程可能和发行版自带的配置冲突导致实际生效值根本不是你以为的那个。排查时记得sysctl vm.swappiness确认一下真实值不要只看配置文件。4.5 结合 swap 空间规划的配套调整swappiness 是如何使用 swap的旋钮但 swap 本身的大小和类型也会影响效果。如果你用的是 swap 文件而不是独立分区它的路径和挂载方式也会影响性能。建议顺序是先规划 swap 空间大小。物理内存 8G 以下是传统 2 倍规则但对大内存服务器我更建议固定分配比如 16~32G甚至不用 swap 直接关掉取决于你对 OOM 的接受度。再决定 swap 放哪。SSD 上 swap 文件放机械盘上完全是两个体验尽量放在 SSD 或 NVMe 盘。最后调 swappiness。空间大小解决能换出多少swappiness 解决倾向换出什么这两者需要配合调整。我遇到过一台机器 swap 文件设在机械盘上swappiness 调到 1但因为临时内存峰值触发了换出机械盘的随机读写延迟直接把服务拖到超时。这种时候你要处理的其实不是 swappiness而是 swap 的介质问题。调参数之前先想清楚 swap 本身的值不值。5. 实测数据与配套调整让优化真正落地5.1 一组有代表性的对比测试为了写这篇内容我在自己的测试服务器上重新跑了一组简单但有效的测试。机器配置32G 内存、SATA SSD、MySQL 8.0表数据量约 20Ginnodb_buffer_pool_size 设 12G。测试动作是连续跑一小时 TPC-C 类读写压测分别观察 swappiness60 和 swappiness10 的区别。指标swappiness60swappiness10swap 峰值占用约 4.2G约 0.3Gpswpin / pswpoutvmstat 采样汇总大量持续换入换出几乎为零平均查询响应时间35ms22ms99 分位响应时间180ms80ms这个结果完全在意料之中swappiness60 时kswapd 认为把 buffer pool 里一部分页换出去也没关系但那些页其实随时可能被查询命中换出去又读回来白白增加 IO。而 10 时匿名页基本钉在内存里page cache 占比高一些整体吞吐上去响应也稳。注意一点这种对比要有足够长的采样周期。swap 行为是慢变量跑五分钟看不出差别至少压测半小时以上再下结论否则很容易被瞬时波动误导。5.2 如何监控 swappiness 调整是否有效很多人在改完参数后只能靠感觉快了一点来判断这不严谨。我建议至少盯三个指标vmstat 5看 si/so 列理想状态是长期为 0 或趋近于 0。如果 si/so 还是很高说明光调 swappiness 不够内存本身可能真的不够用。sar -B 1看 pgpgin/pgpgout判断页换入换出的总量。这个能帮你区分是 swap 流量还是普通文件 IO。业务侧指标响应时间、慢查询数、接口错误率。技术指标再完美业务指标不达标等于白调。还有一个容易忽略的改完 swappiness 之后已经换出去的页不会自动换回来。也就是说就算你把 swappiness 调成 1当前 swap 里那 3G 数据还是躺在那里除非业务重新访问到它们。想快速清掉这些历史包袱可以手动执行swapoff -a swapon -a前提是内存能承担全部 swap 数据的驻留这个操作在业务低峰期做比较好因为 swapoff 会把 swap 里的页全部读回内存对内存和 IO 都有瞬态冲击。5.3 和 tuned、内存回收相关的联动配置如果系统里装的是 tuned 调优服务比如tuned-adm profile throughput-performance它会在启动时按 profile 覆盖部分 sysctl 参数其中很可能包括 vm.swappiness。所以你会遇到一种诡异的场景明明自己改了/etc/sysctl.d/99-swappiness.conf重启之后却又变回去了。原因就是 tuned 的 profile 晚加载把定制配置覆盖了。解决办法要么在 tuned profile 里自定义参数要么直接改/etc/tuned/***/tuned.conf对应的段。更粗暴一点也可以停掉 tuned 服务但我不推荐因为吞吐性能 profile 还管理了很多网络和文件系统的优化参数全扔了不划算。另外内存回收不是只有 swappiness 一个旋钮。vfs_cache_pressure 控制的是 inode 和 dentry 缓存的回收倾向如果系统有很多小文件访问可以适当调低 vfs_cache_pressure 到 50 左右配合 swappiness 一起做内存策略。这两个参数经常被同时优化但注意它们的作用域完全不同一个管匿名页一个管元数据缓存别混为一谈。5.4 场景化的最终推荐配置我把从测试和日常运维中得到的经验整理成一份速查表你们可以直接拿来当初始值表 5-2 不同场景下的推荐初始配置场景swappinessvfs_cache_pressure说明MySQL / MariaDB1 ~ 5默认或 50 ~ 100保护 buffer pool避免 swap 污染PostgreSQL10默认shared_buffers 之外还有很多堆内存要被保护Redis 纯缓存节点1默认内存盘本身就是数据绝不该落盘Java 服务堆内 8G 左右10 ~ 20默认留有少量安全阀防止极端 OOM桌面 Linux小内存 4G80 ~ 10050牺牲后台冷数据保前台流畅大内存文件服务器1 ~ 1010 ~ 50尽量留内存做 page cache并缓存文件元数据需要说明的是这套配置只是起点。每台机器的负载、内核版本、存储介质都不同我强烈建议按测试 -- 观察 -- 再调整的循环来收敛不要照搬一个数字就完事。写在最后一次调错参数的经历最后分享一个亲历的教训。很早之前我在一台 64G 数据库机器上把 swappiness 从默认 60 直接改到 0觉得调小就是好。结果第二天 DBA 找我说实例重启后内存频繁触发 direct reclaimIO 暴涨因为 0 值让内核在内存压力下格外激进地回收文件页而数据库的读扩展场景非常依赖 page cache。后来我把 swappiness 调到 2同时给数据库连接数做了限流问题立刻缓解。这件事给我的教训是参数调优永远要跟着业务特征走不要机械地套最优值。每个参数背后都是一整套内存生命周期管理逻辑值得花点时间把原理弄清楚。你手里的服务器不会因为一个网上推荐的配置就万无一失但有了对机制的理解和完整的监控手段遇到问题时你至少不会被一个看不见的参数卡住。
返回列表