1. 从一次“内存告急”的误判说起前阵子帮一位朋友看他那台跑得好好的服务器监控面板上内存使用率常年贴着 90% 以上他一口咬定是程序有内存泄漏准备连夜重启。我让他先别急敲了free -h一看used那一栏确实吓人但紧跟着的buff/cache也占了一大块而available还剩好几个 G。问题根本不在泄漏而是他把Buffer 与 Cache当成了“被吃掉的可用内存”。这个误会我在一线见过太多次几乎每隔一段时间就要给人解释一遍。Linux 内存里的Buffer和Cache说白了就是内核拿来“借”的缓存空间Buffer 偏向块设备层面的原始数据块缓存Cache 偏向文件内容页缓存两者合起来在free命令里通常显示为buff/cache。它们能做什么能极大减少磁盘 I/O、加速文件读写、让系统在内存富余时把闲置资源利用起来当应用真正需要内存时内核又会主动回收这部分缓存。它解决的核心问题是“内存闲着也是闲着不如拿来提速”同时保证“真要用的时候能还回去”。这篇内容适合谁看刚接触 Linux 运维的新手、被监控面板吓到过的后端开发、以及想搞明白free输出到底怎么读的同行。我会从设计思路讲到实操排查把这块最容易踩坑的知识点掰开揉碎。2. 整体设计思路内核为什么要把内存“借”出去2.1 闲置内存是浪费缓存是双赢理解 Buffer 与 Cache 的第一步是接受一个前提物理内存闲着不用本身就是一种浪费。磁盘的读写速度和内存差着好几个数量级一次机械盘随机读可能是毫秒级而内存访问是纳秒级。如果每次读文件都老老实实走磁盘系统性能会被拖垮。内核的算盘打得很精——把读过的文件页、写过的数据块留在内存里下次再访问同一份数据直接命中内存省掉一次磁盘往返。这就是 Cache 的本质用空间换时间。那为什么还要分 Buffer 和 Cache 两个概念因为它们缓存的对象不同。Buffer 缓存的是块设备上的原始数据块比如文件系统元数据、目录项、超级块这类底层结构它不关心文件内容长什么样只按块来搬运Cache 缓存的是文件内容页也就是你cat一个文件时真正读到的那些字节。早期内核里这两者界限清晰后来随着页缓存page cache统一管理很多场景下它们已经融合但free命令依然习惯性地把它们合并展示为buff/cache。你只要记住一句话它们都是可回收的缓存不是被程序占死的内存。2.2 可回收是底线OOM 之前先“挤水分”设计上最关键的一条约束是缓存必须可回收。内核在分配缓存时用的是“低优先级”策略一旦应用通过malloc、mmap等方式真正申请内存而空闲内存不够内核就会触发回收reclaim把干净的缓存页直接丢弃脏页先回写磁盘再丢弃。这个过程对应用是透明的应用只会感觉到“内存申请成功了”并不会感知到背后缓存被腾挪。正因为有这层机制free命令才专门提供了available这一列。available估算的是“在不触发交换的前提下还能给新应用用多少内存”它已经把大部分可回收的buff/cache算进去了。很多人只看used就慌其实应该看available。我个人的经验是只要available还有余量buff/cache再高都不用管反过来如果available逼近零、同时swap开始频繁读写那才是真需要介入的信号。这个判断逻辑比盯着used百分比靠谱得多。2.3 为什么不干脆把缓存限制死有人会问既然缓存会让人误判为什么不设个上限别让它涨那么高这就涉及取舍了。如果给缓存设一个很小的硬上限内存富余时也无法充分利用等于白白浪费提速机会设得太大又失去限制意义。内核选择的是动态平衡平时尽量多缓存压力大时按 LRU最近最少使用等策略回收。这种“弹性借用”的思路和数据库连接池、线程池的设计哲学其实是一回事——资源池化、按需分配、闲时复用。理解了这层设计意图你就不会再把buff/cache当成敌人而是把它看作内核帮你打工的成果。3. 核心细节解析Buffer 与 Cache 到底缓存了什么3.1 Buffer 与 Cache 的分工对照虽然现代内核里两者在实现上高度融合但从概念和观测角度区分它们仍然有价值。下面这张表是我平时给人讲解时常用的对照帮你快速建立印象维度BufferCache缓存对象块设备原始数据块、文件系统元数据文件内容页、可执行文件代码页典型场景目录遍历、inode 读取、超级块操作读写普通文件、加载动态库回收优先级相对更易回收元数据可重建脏页需回写后回收干净页直接丢观测手段free合并显示/proc/meminfo有Buffers/proc/meminfo的Cached字段生活类比图书馆的索引卡片图书馆里被翻开的书页需要说明的是/proc/meminfo里的Buffers和Cached是两个独立字段而free命令默认把Buffers Cached SReclaimable合并成buff/cache展示。SReclaimable是可回收的 slab 内存比如 dentry、inode 缓存也属于“能还回去”的部分。所以你在free里看到的数字往往比单纯Buffers Cached还要大一些这是正常的。3.2 页缓存page cache才是大头实际生产环境里占buff/cache绝大部分的是页缓存。你读一个大日志文件、跑一次数据库全表扫描、加载一个几百兆的模型文件这些内容都会被页缓存留下来。页缓存以“页”为单位管理常见页大小是 4KB内核用基数树radix tree等结构快速定位某个文件偏移对应的页是否在缓存中。命中就返回未命中才发起磁盘 I/O。这里有个容易被忽略的细节写操作也会进缓存。你echo一段内容到文件数据先写进页缓存并标记为“脏页”然后由内核的后台回写线程如flush内核线程择机刷盘。这就是为什么突然断电可能丢最近写入的数据——它们还在缓存里没落盘。理解这一点你就明白sync命令的意义了强制把脏页刷到磁盘。我见过有人写脚本批量生成文件后立刻断电测试结果数据丢失还怪文件系统其实就是没等回写完成。3.3 缓存回收的触发条件与顺序回收不是随便乱丢的内核有一套优先级。大致顺序是先回收干净的页缓存丢弃即可无成本再回收可回收的 slab然后回写脏页再回收最后才考虑换出匿名页到 swap。这个顺序背后的逻辑很直白——回收成本越低越先做。干净页丢弃零成本脏页要写磁盘有 I/O 成本匿名页换出到磁盘成本更高所以排在最后。触发回收的时机主要有两个一是内存分配时发现空闲页低于水位线watermark二是内核后台线程定期检查。水位线分min、low、high三档低于low就开始积极回收低于min就进入较激进的回收甚至触发 OOM。你在/proc/zoneinfo里能看到这些水位值。日常排查时如果发现系统频繁回收但available仍够通常说明缓存被反复建立又丢弃可能存在“缓存颠簸”这时候要考虑是不是某个程序在反复读大文件却只用一次。4. 实操过程从观测到验证的完整链路4.1 用 free 和 /proc/meminfo 读懂内存现状第一步永远是观测。free -h是最常用的入口但很多人只扫一眼used就下结论。正确的读法是重点看三列buff/cache、available、swap。我通常还会加-w参数看宽格式把buff和cache分开显示free -hw输出里buff和cache会分列方便你判断到底是元数据缓存多还是文件页缓存多。接着看/proc/meminfo拿更细的字段grep -E MemTotal|MemFree|MemAvailable|Buffers|^Cached|Dirty|Writeback|SReclaimable /proc/meminfo这里几个字段值得盯Dirty是待回写的脏页量长期偏高说明磁盘写入跟不上Writeback是正在回写的量SReclaimable是可回收 slab。我一般会连续采样几次看Dirty是否持续增长如果一直涨而不降可能是磁盘 I/O 瓶颈或者回写参数不合理。4.2 制造缓存并观察其增长光看不动手印象不深我建议你亲手做一次实验。找一个几百兆的文件没有就临时生成一个先清一下缓存再读它观察buff/cache的变化。清缓存的命令需要谨慎生产环境别乱敲# 仅测试环境使用生产慎用 sync echo 3 /proc/sys/vm/drop_caches free -hdrop_caches写 1 清页缓存写 2 清可回收 slab写 3 两者都清。清完你会看到buff/cache骤降、free上升。然后读一个大文件dd if/dev/zero of/tmp/testfile bs1M count500 cat /tmp/testfile /dev/null free -h再对比一次buff/cache应该明显回升。这个实验能让你直观感受到“缓存是读出来的”。注意drop_caches只是清理缓存不会影响正在运行的程序数据但会短暂降低性能所以只适合测试环境验证别写进定时任务。4.3 用 vmstat 和 iostat 交叉验证单看内存不够要结合 I/O 指标判断缓存是否在起作用。vmstat 1里的bi、bo是块设备读写速率si、so是 swap 换入换出。如果buff/cache很高而bi很低说明读操作大量命中缓存这是好事如果bi持续很高、buff/cache却涨不上去可能是缓存被频繁回收或者读的是大量一次性数据。再配合iostat -x 1看磁盘%util和await就能判断瓶颈到底在内存还是磁盘。我常用的一个判断口诀缓存高 I/O 低 健康缓存低 I/O 高 可能缺缓存或数据一次性缓存高 swap 活跃 真内存压力。这套组合拳比单看一个数字可靠得多。4.4 调整缓存行为的几个关键参数如果确实需要干预缓存行为/proc/sys/vm/下有几个参数值得了解。vm.swappiness控制换出匿名页的倾向默认 60数据库类场景常调到 10 甚至更低减少不必要的 swapvm.dirty_ratio和vm.dirty_background_ratio控制脏页比例阈值前者是进程同步回写的触发点后者是后台回写的触发点写密集型场景适当调低可以避免一次性大量回写造成卡顿vm.vfs_cache_pressure控制回收 inode/dentry 缓存的倾向默认 100调高会更积极回收元数据缓存。这些参数没有放之四海皆准的值得结合业务读写模式来调。我的建议是先观测再调参一次只改一个改完观察至少一个业务周期。见过太多人一口气改五六个参数出问题根本不知道是哪个引起的。5. 常见问题与排查技巧实录5.1 内存使用率高但服务正常要不要处理这是最高频的问题。答案很明确看available不看used。只要available还有合理余量、swap没有频繁换入换出、业务延迟正常buff/cache高就是健康状态不需要任何处理。强行drop_caches反而会让后续读操作重新走磁盘性能下降。我一般会跟人说缓存高说明内核在帮你干活你该高兴才对。5.2 缓存涨到把内存吃满是不是泄漏缓存理论上不会“吃满到无法回收”因为回收机制一直在。如果真出现available极低且缓存不降要排查两个方向一是是否有大量脏页堆积导致回收受阻看Dirty是否很高、磁盘是否写不动二是是否有程序通过mmap等方式持有大量不可回收的映射。前者调回写参数或排查磁盘后者得看具体进程的smaps。单纯把锅甩给缓存是不对的。5.3 drop_caches 之后性能反而变差这几乎是必然的。清掉缓存后原本命中内存的读操作全部回落到磁盘短时间 I/O 飙升、延迟上升。所以drop_caches只应作为测试手段或极端排障手段绝不能当成“释放内存”的常规操作。我见过有运维脚本定时清缓存结果数据库查询延迟周期性抖动查了半天才发现是这个脚本干的。5.4 常见问题速查表现象可能原因排查方向处理建议used高但服务正常缓存占用看available、swap无需处理available极低、swap 活跃真内存压力vmstat看si/so扩容或优化程序内存Dirty持续偏高回写跟不上iostat看磁盘调低 dirty 比例或换盘缓存反复涨落缓存颠簸看访问模式优化读策略或加内存清缓存后延迟抖动缓存被清确认是否有定时任务移除 drop_caches 定时任务5.5 一个容易忽略的坑容器里的内存统计在容器环境里free看到的往往是宿主机视角或者 cgroup 限制后的视角容易误判。容器内更应该看 cgroup 的memory.stat里面的file字段对应页缓存anon对应匿名内存total_cache等字段能更准确反映容器实际内存构成。很多“容器内存超限被杀”的案例其实是把可回收的 file cache 算进了限制里需要结合memory.limit_in_bytes和回收行为一起看。这块细节多但方向就是容器里别只看 free要看 cgroup 统计。6. 我在实际排查中的几点体会踩过几次坑之后我对 Buffer 与 Cache 的态度从“警惕”变成了“信任但有边界”。信任是因为它的回收机制确实可靠绝大多数所谓“内存告急”都是误读有边界是因为脏页回写、缓存颠簸、容器统计这些细节确实会咬人。我现在看一台机器的内存习惯先free -hw扫一眼available再vmstat 1看几秒 I/O 和 swap两个动作基本就能定性。真要深挖再上/proc/meminfo和 cgroup 统计。这套流程不复杂但比盯着一个百分比靠谱太多。另外提一句drop_caches这个命令我建议你在测试环境多练、生产环境少碰把它当成诊断工具而不是运维手段心态就对了。
阅读完成 · 觉得有帮助?