Linux vm.swappiness 参数详解:从原理到实战的内存回收调优指南
做Linux服务器调优这些年我几乎每接手一台机器都会先看一眼vm.swappiness。很多人一看到swap使用量上涨就开始紧张要么急着把参数改成0要么直接下结论说内存不够用。这话只对了一半swap活跃不一定代表性能要崩vm.swappiness设成0也不是所有场景的万能答案。这篇内容我会把这个参数从原理到实操、从单机到容器环境完整拆开来讲顺便把我踩过的一些坑也一并列出来。无论你是刚入门想搞懂Linux内存回收逻辑的新手还是已经在生产环境调过很多次内核参数的老手这篇都值得你花几分钟过一遍。1. 先搞清楚 vm.swappiness 管的是什么1.1 内存回收时的一次“判断题”要理解vm.swappiness得先知道Linux内核在什么时候会用到它。运行中的进程不断申请内存page cache也会吃掉大量内存来缓存磁盘文件。当空闲内存越来越少、降到某个水位线watermark以下之后内核的kswapd线程会被唤醒开始在内存页里做回收。回收不是随便拆东墙补西墙它面对的主要是两类页文件页file-backed pages来自磁盘文件或文件系统的缓存回收时如果是干净页直接丢弃以后要用再回磁盘读如果是脏页得先写回磁盘再丢弃。匿名页anonymous pages进程堆、栈、匿名mmap等没有文件对应的内存页想回收就得先把内容写到swap设备上也就是“换出”以后访问时再从swap里“换入”。这道选择题就是vm.swappiness参与的地方。内核在计算匿名页回收优先级和文件页回收优先级的时候会用 swappiness 作为权重。值越高匿名页越“愿意”被回收也就是系统更偏向把进程占用的内存换到swap去从而留下更多物理内存给page cache值越低系统越优先回收文件页尽量不把进程内存折腾进swap。很多资料把vm.swappiness说成“系统使用swap的倾向”这个说法方向对但不够精确。它实际控制的是匿名页和文件页之间如何取舍不是简单的“是否使用swap”开关。1.2 默认60不是拍脑袋它是个相对均衡的起点几乎每个发行版默认都是vm.swappiness60。这个值想做的是给文件缓存和匿名页一个相对公平的竞争机会。文件缓存对读密集负载很重要反复读同一个数据库文件时page cache命中能让延迟低很多匿名页则是正在运行进程的直接内存换出和换入都有代价。60这个值在多数普通桌面和通用服务器上表现得比较均衡不会让系统特别偏向某一边。但“均衡”不等于“最优”。生产环境负载类型千差万别有些机器主要跑数据库有些是静态资源Web服务有些跑批任务它们对内存回收的敏感性完全不一样。默认值只能说安全不能说合适。这也是为什么我们需要针对自己场景去调它的原因。1.3 0和100的真实含义别把倾向当百分比这里必须澄清一个高频误区swappiness100不是说“系统会用掉100%的内存去做交换”swappiness10也不代表“只有10%的内存会被交换”。它只是控制内核回收匿名页相对于文件页的“倾向程度”。拿极端值举例swappiness0内核会尽量不回收匿名页。注意是“尽量”不是“绝不”。一旦内存压力非常大、文件页又回收不出足够空间时匿名页依然会被换出否则系统只能OOM杀进程。在部分内核版本上极端内存压力下如果死死压住匿名页回收反而可能出现直接回收反复扫描、CPU使用率异常升高的情况。swappiness100内核把匿名页和文件页放在同等优先级上考虑回收甚至在某些情况下更偏向换出匿名页。它不代表系统会立刻把所有内存倒进swap只代表当内存不够时内核认为换出冷匿名页和回收缓存都是合理选项。用生活化一点的方式理解内存是你的桌面swap是旁边的抽屉柜。文件页是已经看完可以随手丢进碎纸机的资料匿名页是你正在填写的表格。swappiness就是决定你“多勤快地把桌面上的草稿挪进抽屉”的旋钮。设成0并不代表你永远不会把草稿放进抽屉只代表你希望桌面实在堆不下了才动手设成100则代表你更倾向于保持桌面整洁哪怕代价是频繁开抽屉。2. 不同场景下该设置多少先给结论再讲逻辑2.1 数据库类负载低值优先但不是越低越好如果机器主要跑MySQL、PostgreSQL这类数据库我的建议通常是vm.swappiness10。数据库的buffer pool、shared buffers会把热数据尽量留在内存里这类进程的匿名内存一旦被换出一次随机读的延迟可能从微秒级恶化到毫秒级甚至更高对查询性能影响很大。所以我们要尽量让匿名页留在物理内存里让系统优先回收文件页来满足内存需求。但为什么不是0因为需要预留一点弹性。数据库进程在一段时间内可能会因为连接数增加、临时结果集变大而突然吃掉更多内存这时候如果没有可用swap内核别无选择只能在内存压力下去激进扫描匿名页极端情况下还会触发OOM Killer把数据库进程杀掉。相比偶尔牺牲一小部分冷匿名页直接挂掉数据库是更不能接受的结果。我自己的习惯是纯内存型组件比如开启了持久化的Redis集群节点可以设10到30有大量文件缓存的MySQL、PostgreSQL设10是比较均衡的选择只有明确知道这台机器绝不会发生内存突发增长应用对延迟极度敏感时才考虑设0并且一定要配好告警。2.2 容器与 K8s 环境宿主和 cgroup 要分开看很多K8s节点上部署着各种微服务但我不建议直接在宿主机上一刀切改成0因为容器场景还要看cgroup这一层。在cgroup v2环境下每个cgroup都有自己的memory.swappiness默认继承父级。即使你在宿主机把vm.swappiness设为10容器里的进程在触发自身memory cgroup的内存回收时用的可能是当前cgroup的memory.swappiness而不是宿主机全局值。如果宿主机是systemd管理可以针对某个service单独设置systemctl set-property my-service.service MemorySwapMax0注意MemorySwapMax是限制swap使用量和swappiness是两回事但两者经常配合使用。如果你的容器直接跑在cgroup v2路径下也可以查看和临时调整cat /sys/fs/cgroup/memory.swappiness echo 10 /sys/fs/cgroup/memory.swappiness这个修改在cgroup销毁后会失效生产上要落到容器运行时配置或systemd单元文件里。在K8s里想可靠地调整节点内核参数通常建议走节点级sysctl配置或专门的初始化DaemonSet而不是手工进容器里改。因为容器内改/proc/sys通常需要特权而且重启镜像就丢。2.3 桌面系统与压缩 swap反而可能调高服务器上我们忌讳频繁swap但在桌面系统里情况可能相反。不少桌面发行版默认用zram或zswap把“swap”压缩后放在内存里而不是写进磁盘。这样一来换出匿名页的代价比写到机械盘或普通SSD低得多内存紧张时早点把冷页压缩存放反而能腾出更多物理内存给热数据和应用缓存。在这种场景下把swappiness调高一些比如80到100是合理的。比如Fedora Workstation在zram方案下把默认swappiness调到了较高值这是有意的设计。反过来说在zram环境里还死守swappiness0相当于明明有廉价的“内存压缩换出”机制不用非要等到物理内存极满再去触发更激进的文件回收反而浪费了zram的设计优势。2.4 内存充裕的新服务器保留 swap 比调零更有意义不少新服务器内存很大比如256G、512G很多同学心想“反正内存够大swap也没用”直接关swap或者设0。我的观点是内存再大也要保留一点swap尤其云主机和虚拟机环境。保留少量swap不是为了日常用它而是给内核一个“紧急缓冲垫”。如果应用层出现内存泄漏或者某个Java进程的堆突然暴涨内核可以先换出冷页来度过高峰而不是立刻进入直接回收甚至OOM。其次很多系统的systemd-oomd、earlyoom之类的机制会监测内存压力它们也需要swap配合辅助决策。没有swap时内存水位一旦见底可用的兜底手段就会少一个。内存大的机器我一般会做两件事挂一个2G到4G的swap文件并把vm.swappiness控制在10到30。这样既不会让系统动不动就碰swap又在关键时刻留了一条退路。3. 实操落地查看、修改、持久化一条龙3.1 先摸清系统当前状态动手改之前先看看当前值和整体内存分布cat /proc/sys/vm/swappiness sysctl vm.swappiness再看一下当前swap设备和用量free -h swapon --show顺便确认系统处于cgroup v1还是v2后面配置容器时需要stat -fc %T /sys/fs/cgroup如果输出是tmpfs说明是cgroup v2如果输出是cgroup则是v1。cgroup v1系统对memory.swappiness的cgroup级配置支持有限主要依赖全局参数。3.2 临时调整与永久生效临时生效一条命令就搞定sysctl -w vm.swappiness10这个操作立即生效不用重启但重启后失效。想要永久生效写入sysctl配置目录echo vm.swappiness10 /etc/sysctl.d/99-swappiness.conf sysctl --system这里我推荐在/etc/sysctl.d/下单独建一个.conf文件而不是直接往/etc/sysctl.conf里塞。原因是独立文件可追溯、可维护以后要换值只改一个文件就行也不会和系统里其他配置冲突。sysctl --system会按顺序加载这个目录下的配置文件99前缀在多数发行版上能保证较晚生效优先级更高。有一点要提醒云服务器如果用镜像模板批量创建光在单机里写sysctl.conf还不够需要把配置做进模板或初始化脚本里否则下次扩容出来的新机器又会回到默认值。3.3 修改后确认生效改完之后别急着走验证一下sysctl vm.swappiness cat /proc/sys/vm/swappiness如果目标平台有systemd并且你针对某个service设置了swap相关限制可以看看实际状态systemctl show my-service.service | grep -i swap对于长期运行的服务器我更建议把vm.swappiness纳入配置管理工具Ansible、Puppet、SaltStack都可以用统一的方式去管理。这种参数改动本身不大但一旦几百台机器配置不一致排查问题时很容易被忽略。4. 如何验证参数真的起了作用4.1 关注 si/so 和 pswpin/pswpout而不是 swap 剩余量很多人改完参数就盯着free -h里的Swap used看发现它没降就以为参数没生效。这是不对的。vm.swappiness影响的是内存回收时的倾向已经换出到swap的数据只要没有新的内存压力不会只因为改了参数就大幅回收到内存。你应该看的是swap交换活动。用vmstat看siswap in和soswap outvmstat 1想观察累计值就看/proc/vmstatgrep -E pswpin|pswpout /proc/vmstat如果swappiness调低之后so和pswpout在相同内存压力下明显下降说明匿名页被换出变少了参数作用就体现出来了。另外可以看看匿名页和文件页的回收计数grep -E pgsteal_anon|pgsteal_file /proc/vmstat低swappiness通常意味着回收的页里文件页占比更高也就是说系统更多通过丢弃缓存来释放内存而不是把进程内存搬去swap。4.2 用真实负载做一次小规模验证生产环境不敢乱搞可以在测试环境模拟一次内存压力。思路是先读取一个大文件让page cache占掉一部分内存。记录当前的pswpin、pswpout、pgsteal_anon、pgsteal_file。用stress-ng分配大量内存观察内存压力下的回收行为。在swappiness60和swappiness10两种设置下各跑一轮比较swap相关计数。一个相对安全的做法# 先制造page cache dd if/dev/zero of/tmp/bigfile bs1M count4096 cat /tmp/bigfile /dev/null # 记录基线 cat /proc/vmstat | grep -E pswpin|pswpout|pgsteal_anon|pgsteal_file # 制造内存压力 stress-ng --vm 2 --vm-bytes 80% --timeout 30s注意stress-ng的参数要根据机器实际内存调整别直接把系统压死。测试环境建议在VM里做生产环境更推荐用真实业务流量观察慢速变化而不是主动制造极端压力。4.3 长期观察的手段短期压测只能看出即时反应我更习惯配合监控系统看长期曲线。比较实用的指标是swap used变化量、page cache命中率如果应用能统计、系统负载和磁盘IO的关联。如果磁盘是机械盘swap IO和磁盘utilization一起飙升时多半是swappiness偏高导致的匿名页换出过于频繁调低之后这类峰值通常会明显减少。有些同学会问swappiness改了之后需不需要重启应用。一般不需要因为这只是内核回收策略应用进程无感知。除非你的应用内部有类似JVM的GC日志记录了swap事件那也只是观察层面受影响进程本身不需要重启。5. 常见的坑和排查记录5.1 重启后配置丢失这是我见得最多的问题sysctl -w改完当时生效重启后发现自己回到60。原因就是没写进配置文件或者写的位置不对。有些云镜像里的/etc/sysctl.conf可能被云初始化脚本覆盖你写进去的内容运行后又被重置。解决办法是确认操作系统是否有cloud-init或类似配置管理组件把自定义配置放到/etc/sysctl.d/下高优先级文件并在云平台的初始化脚本里固定下来。改完一定要用sysctl --system重新加载别只重开一个shell去看那容易产生“改了没生效”的错觉。5.2 内存明明没满swap 却还在涨有一种情况经常让人困惑free -h显示可用内存还很多Swap used却在缓慢上涨。这不一定是swappiness设高了更多时候是某些进程主动调用madvise、mlock相关机制或者内存整理时触发了swap。另一个常见原因是内核在后台预测到未来可能有内存压力会提前把一些冷页换出到swap这个行为在部分内核版本里甚至和swappiness的关系没那么直接。遇到这种问题先按进程查swap占用for f in /proc/[0-9]*/status; do awk /Name|VmSwap/{printf %s , $2} END{print } $f; done | sort -k2 -n -r | head -20找出谁在占用swap再针对那个进程去分析原因。如果是个长期不访问的守护进程它占swap本身不算什么大问题如果是数据库进程在占swap那就要检查内存分配策略和连接池设定了。5.3 容器内改 swappiness 不生效在容器里执行sysctl -w vm.swappiness10有时候会提示Permission denied有时候看起来成功了但实际上没作用。原因是容器共享宿主机内核这个参数在/proc/sys/vm/下面属于全局参数普通容器没有权限改即使有权限改了也会影响整个宿主机而不是只影响当前容器。正确做法分两层有特权时在宿主机层调整全局参数cgroup v2环境下用memory.swappiness对特定cgroup做限制再配合K8s Node级别的sysctl配置做统一管理。总之别指望在容器内部一条命令解决所有问题。5.4 调了参数还是频繁 swap先看内存分配方式如果swappiness已经设到10甚至5swap使用还在上升这时候不能只盯着参数要去看进程的内存分配习惯。很多服务端程序存在内存碎片问题或者用了过多的匿名映射还有的GC类语言在高峰时会突然向内核申请一大块内存这些都会造成瞬时swap写入。还有一种情况是内存总量确实不匹配负载比如4G内存跑Java应用堆就给了3G那swappiness设成再低也没办法物理内存已经见底内核必须换页来腾空间。这种情况下我的建议顺序是先优化应用内存占用和堆配置再看是否需要扩容内存最后才考虑微调swappiness。参数只是最后一道调节阀不能替代容量规划。5.5 顺带提一个容易搞混的参数vm.vfs_cache_pressure调swappiness的时候经常有人把vm.vfs_cache_pressure一起改这个参数控制目录项和inode缓存回收的倾向。数值越高内核越积极回收文件系统元数据缓存默认100调低到50可以让这类缓存更持久。它和swappiness并不是一回事但两者会共同影响内存回收的整体行为。如果你在做内存调优建议把swappiness、vfs_cache_pressure、page cache相关策略放在一起看别只改一个就下结论。我踩过的一个坑是把vfs_cache_pressure直接调到0结果某些场景下目录项缓存长期占用大量内存导致其他进程可用内存反而变少。后来我把它控制在50到100之间效果比极端值好得多算是“过度调优”的典型反面教材。写到最后简单说一点我的个人体会。vm.swappiness没有一劳永逸的标准答案网上流传的“一律设0”和“设100”都过于极端。合理的做法是先想清楚你的负载模型内存里是热数据重要还是快速响应文件读更重要是宁可轻微交换保命还是宁可让进程常驻内存绝不换出。然后把测试环境当试验场结合监控数据去找那个平衡点。我目前最常用的组合是普通Web服务10到30数据库10左右桌面或zram环境80到100内存极大且负载很平稳的机器才考虑0并配好告警。如果哪天你在生产环境改了参数之后反而出现莫名卡顿不要犹豫先回滚配置再从内存分配和应用行为的角度去查。参数是死的系统是活的把原理吃透比记住某个推荐值有用得多。