ARTICLE DETAIL

资讯详情

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

Linux内存告警别慌:读懂buff/cache与内存回收的真相

Linux内存告警别慌:读懂buff/cache与内存回收的真相 上个月帮一个朋友排查服务器内存告警他发来一张free -h的截图16G 的机器used 显示 14.9Gavailable 只剩几百 MB监控平台一直在告警。他第一句话是内存不够了吧要不要加一条我让他先跑个 top 看是哪个进程在占内存结果翻了半天没有一个进程超过 1G。我让他再看一眼 free 里 buff/cache 那一列他截图回来12.8G。这个场景我见了太多次——Linux 的 buffer/cache 把 used 撑得极高看起来像内存爆了实际上系统健康得很。这篇就完整聊聊 buffer/cache 为什么会这么高什么时候真的需要管以及怎么处理才不踩坑。1. 看 free 输出先别慌内存报告的假高不等于真高1.1 先用一个例子学会读 free我习惯直接free -h输出长这样数值因系统而异$ free -h total used free shared buff/cache available Mem: 15G 2.1G 1.2G 87M 11G 12G Swap: 2.0G 0B 2.0G很多人第一眼看到的是 used 2.1G觉得还好。但有的机器 used 会显示到 14G 以上因为那一列把 buff/cache 也算进去了。老版本的 free 甚至没有 available 列更容易吓到人。真正要看的指标有两个available估算在不触发 swap 的前提下还能给新进程分配多少内存。这个数字把可回收的 page cache 也算进去了所以它通常比 free 大很多。buff/cache块设备缓冲加页缓存的总和它由可回收缓存和不可回收缓存共同组成下一节细说。如果只看 used看到 90% 就以为内存爆了这是九成内存占用过高误报的来源。1.2 为什么 Linux 要把内存拿来缓存文件Linux 内核的内存管理哲学和普通桌面系统不一样空闲内存就是浪费。内存比磁盘快几个数量级文件数据读一遍之后把副本留在 page cache 里下次再读直接从内存返回延迟降一个量级吞吐升一个量级。这就是为什么系统跑了一段时间后buff/cache 会一路涨上去——你访问过越多的文件缓存就越多。可以用饭店备菜来理解生意好的饭店后厨会把常用菜提前备好客人一点就下锅翻台就快。Linux 也一样把读取过、写入过的文件内容留在 cache 里等真正有进程申请内存时内核再把缓存页回收、分配出去。回收干净页缓存的开销非常低因为数据在磁盘上本来就有副本丢掉缓存页不影响数据完整性只是下次再读要重新走磁盘而已。所以系统跑久了 cache 高恰恰说明内存利用得很充分不是内存泄漏。1.3 除了文件缓存shared 和 tmpfs 也会让 cache 虚高这里还有一个容易误判的点tmpfs 这类基于内存的文件系统挂在 /dev/shm 或容器里的某些目录时数据是直接存在物理内存里的。free 输出里 shared 列专门显示这部分有些发行版也会把它并进 buff/cache 展示。tmpfs 占用的内存不能被回收因为里面有实际数据掉电就没了。判断方法df -h /dev/shm看 tmpfs 上限cat /proc/meminfo | grep Shmem看当前占了多少。如果容器里应用大量使用共享内存宿主机上 shared 可能几个 G这部分是没有办法通过 drop_caches 释放的。我见过有人以为这是 page cache清完发现一点没降最后排查到容器共享内存配额上去了。2. buffer 和 cache 根本不是一回事内核里两套机制的差异2.1 cache文件的热数据仓库cache 在 Linux 里通常指 page cache是文件数据在内存中的缓存。按页默认 4K管理读文件时先查 cache命中就直接把数据返回给应用没命中才发起磁盘 IO。写文件时数据也是先写进 cache标记为脏页Dirty再按策略异步刷回磁盘这个刷盘过程叫 writeback。所以复制一个大文件时你会看到 cp 瞬间结束——数据还在 cache 里排队——之后磁盘还会持续写一会儿。如果 Dirty 页积压过多进程写文件反而会变慢因为内核要腾出空间或强制写回。写压力很大的机器dirty_ratio 这类参数会直接影响写入延迟。我处理过一个日志服务每个请求都要回放日志文件。刚开始读什么命不中什么QPS 上不去后来 page cache 把热日志都缓存住了QPS 明显提升。这不是玄学是 page cache 的本质作用。2.2 buffer块设备层的缓冲buffer 的资历比 cache 更老早期 Linux 版本里它独立管理块设备层面的缓冲比如文件系统元数据、超级块、目录项等。后来内核把 buffer 和 page cache 统一管理了free 命令也把 buff 和 cache 合并显示成一项。所以现代 Linux 里你很难单独把 buffer 拆出来看free 输出中的 buff/cache 主要就是 page cache 加少量块设备缓冲。buffer 单独很大时常见于跑数据库或频繁修改文件系统元数据的场景比如大批量创建/删除文件、日志轮转、格式化磁盘时。这种时候不要惊讶它不是额外占用判断依据还是要看整体内存压力和 available。2.3 别忽略 Slab把 used 撑高的隐形内存很多人在 top 里找不到大进程但 free 显示 used 很高除了 buff/cache 外还有个容易忽略的点是内核 slab。dentry 缓存和 inode 缓存就存在 slab 里dentry 是目录项的缓存inode 是文件元数据的缓存。如果系统里小文件特别多——比如对象存储目录、Git 仓库、海量日志目录——光 dentry 和 inode 缓存就能吃掉好几个 G而且这部分会计入 used不会显示在 buff/cache 列。查看命令$ cat /proc/meminfo | grep -E SReclaimable|SUnreclaim SReclaimable: 123456 kB SUnreclaim: 12345 kB $ slabtop -s c # 按 cache 大小排序查看SReclaimable 表示可回收的 slabSUnreclaim 表示不可回收的。大量小文件场景SReclaimable 会很高这一部分用 drop_caches 的 2 级可以回收后面会讲。3. 什么样的占用过高才算真故障用四条红线做判断3.1 判断基准不是 used是 available 和 swap想判断一台机器是不是真缺内存我和团队落实的规则很简单available 长期低于总内存的 10%vmstat 里 si/so 持续不为 0或者 swap used 持续上涨触发 OOMdmesg 里能看到 oom-killer 的日志容器场景下 cgroup 内存达到 limit业务容器被频繁回收或重启。如果上面四条都不满足哪怕 buff/cache 占掉 90%也完全可以不管。我之前处理过一台机器只有 2G 内存cache 常年 1.7Gavailable 还有 300MB业务是 Nginx 静态文件服务跑得飞快——因为静态文件全在 cache 里这反而是性能保障。拿上面的 free 输出举例15G 内存used 2.1G 看起来不高但 buff/cache 11Gavailable 高达 12G。这意味着就算有进程突然申请 10G 内存内核会在几毫秒内回收大量缓存页满足它根本不会触发 swap。这种机器怎么压都不会垮。3.2 四条红线其实对应四种场景频繁 swap说明匿名内存和 cache 都不够分内核只能把内存页挪到磁盘这是最明确的内存不足信号。触发 OOM内核直接开杀进程无故消失已经算故障。cgroup 限制宿主机有内存但容器被限制在 2G容器内 cache 超过配额就会挤压进程内存甚至 OOM。这种情况要看docker stats或/sys/fs/cgroup里的统计不能只看宿主机 free。应用性能下降如果应用严重依赖 page cache典型如 MySQL、PostgreSQL、MongoDB把缓存清了会导致读放大查询延迟飙升。这时要维护的是缓存命中率而不是追求 free 列好看。3.3 运维操作带来的虚高是良性的日常备份、日志采集、代码发布解压、数据导入这类操作都会批量读不少文件跑完以后 cache 自然会涨到很高。这种占用过高不需要任何处理过一段时间会被新数据覆盖或者在有内存需求时被回收。如果实在看不下去最多在任务低峰清一次别做成每分钟都去清。4. 排查链路先搞清楚 cache 里存的是什么再决定动不动手4.1 五步排查法如果确实决定要处理别上来就echo 3。按这个顺序排查第一步看整体和细节$ free -h $ cat /proc/meminfo | egrep MemTotal|MemFree|MemAvailable|Buffers|^Cached|Dirty|Writeback|SReclaimable|SUnreclaim|Shmem重点看 Dirty 值。Dirty 是等待写回磁盘的脏页量如果它一直徘徊在几个 G说明写入压力大或者磁盘写入太慢。这种情况要先解决写盘性能而不是清 cache否则清完很快又堆起来。第二步手动触发一次写回$ sync $ free -h执行 sync 之后Dirty 会降下来写缓冲变成干净页。这一步也能验证 cache 高的原因是不是写入缓冲区积压。第三步看 slab$ slabtop -s c第四步看进程内存占用排序$ ps aux --sort-rss | head -20top 默认按 CPU 排排查内存问题要用 RSS 倒序。注意进程 RSS 不含 page cache如果进程本身不大、RSS 也不高但系统内存却高那基本可以锁定在 page cache 或 slab 上。第五步看 IO 与内存扫描情况$ vmstat 1 5 $ iostat -x 1 3vmstat 里 si/so 看 swapbi/bo 看块设备读写iostat 看磁盘 util 和 await。如果磁盘 util 很低但 bo 很高说明大量数据是从内存 cache 异步写回磁盘的存在脏页积压。4.2 区分可回收和不可回收内存排查的核心就一句话搞清楚有多少内存可回收、有多少是硬占用。可回收干净的 page cache、SReclaimable 的 slab、被换出的 swap 缓存等。不可回收进程匿名内存、共享内存、mlock 锁定的页、SUnreclaim 的 slab。/proc/meminfo 里的 MemAvailable 本质上就是内核自己算的可分配内存余量它把可回收的 cache 都当作可用了。所以 available 比 free 更有决策意义。4.3 观察回弹清完还涨回去就是业务在持续产生缓存排查时要区分是历史缓存还是持续新增清一次 cache具体命令见下节如果几分钟后 cache 又涨回高位说明业务持续在大量读写文件缓存高是常态如果保持低位说明只是历史操作留下的残余缓存清一次就完事。这一步能帮你拍板是调参数、优化应用还是干脆加内存。5. 动手回收的正确姿势drop_caches 和内核参数5.1 drop_caches三个级别的手动回收/proc/sys/vm/drop_caches 是内核提供的缓存回收开关写入不同的值回收不同类型的内存写入值回收内容适用场景1page cache文件页缓存读大文件/大数据导入后的残留缓存2dentry 和 inode 缓存海量小文件、目录操作导致的 slab 高3上面两者全部需要尽快回收内存时操作方式$ sudo sync $ sudo sh -c echo 3 /proc/sys/vm/drop_cachessync 先执行把脏页刷回磁盘避免把待写回的数据丢掉。虽然 drop_caches 本身不会强制丢弃脏数据但生产环境稳妥起见先 sync 永远是正确习惯。注意清完之后可用内存未必立即暴涨。drop_caches 的语义是告诉内核可以把这些缓存回收掉内核会尽快回收free 里 available 会逐步上升。清完再执行free -h验证一次观察 Dirty 有没有彻底降下去。小坑某些老内核写入 3 可能提示无效参数遇到这种情况先确认内核版本别慌。5.2 内核参数调优让缓存更听话如果业务注定会产生大量缓存靠手动清不是长远之计调这几个参数更稳vm.swappiness默认 60控制回收 page cache 和 swap 匿名页的倾向。值越高越积极使用 swap。对多数服务器设为 10 左右可以降低 swap 触发概率让内核优先回收缓存而不是把进程内存挪到磁盘。vm.vfs_cache_pressure默认 100控制内核回收 dentry/inode 缓存的速度。设为 200 时更积极回收 slab适合海量小文件场景设为 50 则倾向保留缓存适合频繁查目录、文件元数据的场景。vm.dirty_ratio默认 20百分比脏页占内存比例达到这个值进程写入会被阻塞强制同步写回。写压力大的机器可以适当调小到 10 或 5避免一次性积压太多脏页导致 IO 尖峰。vm.dirty_background_ratio默认 10百分比达到这个比例内核在后台开始写回。一般设置成 dirty_ratio 的一半。vm.min_free_kbytes内核保留给紧急分配的最小空闲内存。内存分配紧张时防止卡死但设太大会浪费内存通常不主动调。常用组合示例$ sudo sysctl -w vm.swappiness10 $ sudo sysctl -w vm.vfs_cache_pressure200 $ sudo sysctl -w vm.dirty_background_ratio5 $ sudo sysctl -w vm.dirty_ratio10临时生效用 sysctl -w要永久生效写入 /etc/sysctl.conf 然后sysctl -p。5.3 长期策略先改参数再考虑定时清理如果业务会持续产生缓存把上面参数调对通常比定时清理更优雅。定时清理只适合特定场景比如每天凌晨跑完批处理清理一次让监控不误报。不建议做成高频率、无差别的缓存清理任务——代价在下一节讲。真要写定时清理也要用 sync drop_caches 3并且避开业务高峰时段。6. 我在生产环境踩过的坑清缓存这件事的副作用比想象中大6.1 三个真实副作用第一缓存命中率瞬间暴跌。数据库这种重度依赖 page cache 的应用你把 cache 清了等于把热数据全赶出内存接下来一段时间所有查询都去打磁盘。之前帮客户排查过一个案例某巡检系统每天凌晨 4 点 echo 3 清缓存结果每天早上 9 点数据库慢查询飙升赶巧碰上业务高峰用户反馈卡顿。后来把清理任务去掉慢查询就消失了。第二回弹比你想的快。只要业务还在正常读写文件清完几十分钟内 cache 又会涨回去。你清掉的是一整天的热数据业务还要重新把数据读一遍等于额外增加大量 IO。清了半天性能还倒贴。第三高峰期清缓存放大抖动。清缓存本身不阻塞进程但它触发大量缓存回收后紧接着的读请求全部 miss会以突刺的形式压到磁盘上。如果磁盘本身性能一般这个突刺可能直接把 IO 延迟拉满。所以我的原则是生产环境的缓存清理必须先评估业务影响并在低峰期操作。能不动就不动。6.2 什么时候该考虑加内存而不是调参数排查到最后如果出现下面几种情况调参数已经解决不了本质问题业务本身的活跃数据量就超过物理内存page cache 永远不够用反复被淘汰、反复从磁盘读数据库/Redis 等工作集远超内存命中率长期上不去清完缓存后 available 依然持续走低——那往往不是缓存问题是进程或 cgroup 限制问题。这时候加内存往往是性价比最高的方案。内存价格不算高但业务因慢查询、OOM 带来的损失远高于硬件成本。别为了省那一点预算让整个团队天天盯内存。6.3 监控告警和建议脚本最后说一下监控告警怎么配。正确做法是不要对 buff/cache 列做告警要对以下指标告警MemAvailable 占比低于阈值比如 10%swap used 非 0 且持续增长发生过 OOMdmesg或容器重启。下面这个简单脚本可以挂在 cron 里检查#!/bin/bash # check_mem.sh - 检查 available 内存和 swap 使用率 TOTAL$(free -m | awk /^Mem:/ {print $2}) AVAIL$(free -m | awk /^Mem:/ {print $7}) PCT$((AVAIL * 100 / TOTAL)) echo $(date %F %T) available ${AVAIL}MB / ${TOTAL}MB (${PCT}%) if [ $PCT -lt 10 ]; then echo 警告: available 内存低于 10% fi SWAP_USE$(free -m | awk /^Swap:/ {print $3}) if [ $SWAP_USE -ne 0 ]; then echo 注意: swap 已使用 ${SWAP_USE}MB fi把它加到 cron 里每 5 分钟跑一次只记录异常输出。这类脚本比直接清理 cache 有意义得多。最后聊一个我这几年排查内存问题最深的体会搞清楚 buffer/cache 为什么高比急着让它变低重要一百倍。绝大多数时候它是 Linux 在帮你干活而不是在偷你的内存。真正需要动手解决的永远是 available 不足、swap 在涨、业务在变慢这些底层信号。学会用数据判断比背一堆清理命令管用得多。
返回列表