ARTICLE DETAIL

资讯详情

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

Linux内存buff/cache高?该不该清看available

Linux内存buff/cache高?该不该清看available 写这篇东西的起因是很典型的运维场景。某天早上刚到工位同事发过来一张监控截图说新上的文件服务内存要爆了。我看了一眼free -h输出里buff/cache占了十几个Gfree那列只剩几百M正常人的第一反应都是“内存不够了”。但再往下看一行available还非常健康。类似的告警我处理过太多次了大问题基本没有小误会确实不少。所以今天把Linux下 buffer/cache 内存占用偏高这件事彻底说清楚它到底是什么什么时候该管什么时候压根不用管以及真正需要处理的时候应该怎么下手。我在这一两年的日常排障里发现很多人对缓存的理解停留在“缓存占内存 内存不够”的层面一看到 cache 高就跑去清缓存结果业务性能反而下降了。这篇内容主要给两类人看一类是刚上手Linux服务器运维、看到free命令就心慌的新人另一类是已经踩过 cache 清理坑、想知道背后原理和正确调优姿势的实践者。下面要讲的东西我都会尽量按“为什么会这样”来拆解而不是只给命令。1. buffer/cache到底在“吃”什么内存1.1 先搞清楚内核把内存花在哪了Linux内核有一个非常朴素的理念空闲内存放着不用就是浪费。你买回来的内存要么给正在运行的进程用要么给内核做各种缓存唯独不应该长期躺着睡大觉。所以在Linux上只要内存没有正经用途内核就会把它拿去干“边角料”的活儿这些活儿最终都归到了buff/cache这个统计项里。具体来说buff/cache里面大体装了三类东西Page Cache页缓存文件内容被读进内存后留下的副本。你cat过一个日志文件grep过一个导出表内核就会把对应磁盘数据留在内存里。下次再读同一份文件直接命中内存不用再走磁盘 I/O。这是所有文件读写性能的关键之一。Buffer Cache块缓存历史上是用来缓存块设备原始数据的比如磁盘、分区、文件系统元数据等。在现代内核里buffer 和 page cache 在统计上经常合并展示概念上可以把它们都理解为“内核在给磁盘I/O做加速的缓存区”。Slab / dentry / inode 缓存文件系统为了快速查找路径、解析文件属性会把目录项dentry和索引节点inode也缓存在内存里。遍历过一个超大目录、批量创建过海量小文件内存里就会积攒大量这类元数据缓存。它在free里归入buff/cache大项具体看/proc/meminfo里的Slab字段。这里有个很关键的点上面这三类东西绝大多数都是可回收reclaimable的。什么意思就是当业务进程真的需要内存时内核会把它们直接淘汰掉把内存腾出来给业务用。你可以把 page cache 想象成办公桌上摊开的一堆文件夹副本当你要放新文件、开新项目时管理员会把副本收走把桌面腾出来。真正的“占用”是指那种收不回来的占用比如进程的匿名内存堆、栈那才是硬占。1.2 一块能随时交还的内存本质上不算“占用”所以这里就引出一个特别反直觉的结论free命令里的free列低不代表内存不够用。因为真正的内存余量要看available而不是free。我之前遇到过一位刚做运维的朋友盯着监控面板上的“已使用内存”发愁非说某台机器内存告警。我让他跑了一遍free -h再对照/proc/meminfo里的MemAvailable他很快就意识到自己虚惊一场了。这个误区太普遍了我甚至觉得它已经成了Linux新手入门的第一道坎。内核之所以鼓励“吃了吐、吐了吃”的缓存策略本质是为了提升系统整体吞吐。进程读文件时产生磁盘I/O是最慢的操作之一如果每次读同一个文件都要重新从硬盘搬数据那服务性能会惨不忍睹。缓存就是拿空间换时间。所以buff/cache偏高在绝大多数场景下是系统健康、资源被合理利用的表现而不是故障信号。2. free命令的正确打开方式2.1 一份典型的free输出该怎么读先来看一份典型的输出$ free -h total used free shared buff/cache available Mem: 15Gi 2.1Gi 1.2Gi 128Mi 12Gi 12Gi Swap: 2.0Gi 0B 2.0Gi很多人第一眼会慌used才2.1Gfree才1.2G总共15G内存怎么只有1.2G没用别急再看buff/cache那列12G。说明那12G是内核拿去做缓存了。再看最后一列available12G。这才是系统真正能分配给新程序的可用内存量。available的计算逻辑大致是free列的剩余内存 buff/cache里可以被回收的部分 - 内核自己预留的等。也就是说available已经提前把那部分“能腾出来”的缓存算进去了。所以看内存是否紧张优先看available而不是free。2.2 available才是你该看的那个数从实际操作层面讲我建议在日常巡检和监控报警里把阈值建立在available上。只要available充足哪怕free是零系统也能正常分配内存给新进程不会出现OOM内存耗尽或者Swap换页。这里再介绍一个我个人很习惯的命令组合比单纯看free -h更细一点$ cat /proc/meminfo | grep -E ^(MemTotal|MemFree|MemAvailable|Buffers|Cached|Dirty|Writeback|Shmem|Slab|SReclaimable|SUnreclaim): MemTotal: 16264340 kB MemFree: 123456 kB MemAvailable: 12789432 kB Buffers: 345678 kB Cached: 10234567 kB Dirty: 1024 kB Writeback: 0 kB Shmem: 2345 kB Slab: 1234567 kB SReclaimable: 1100234 kB SUnreclaim: 134333 kB/proc/meminfo里的字段能帮你判断缓存的构成。Dirty表示“待写回磁盘的脏页”Writeback表示“正在写回磁盘”的数据这两个值如果持续很高系统可能真的有问题。Shmem是共享内存/tmpfs占用这个东西在free里被统计在share和buff/cache里但它并不像普通Cache那样可以干净地回收后面会专门讲。SReclaimable是可回收的slab内存比如dentry/inode缓存SUnreclaim是不可回收的slab这个值通常不大如果异常增长也需要关注。2.3 什么情况下cache高真的出事了那buff/cache高就一定没问题当然不是。判断标准要看两个附加条件一是available是否持续偏低二是buff/cache本身是不是包含了不可回收的部分。举一个典型的反例$ free -h total used free shared buff/cache available Mem: 15Gi 2.1Gi 800Mi 8.0Gi 12Gi 2.2Gi Swap: 2.0Gi 0B 2.0Gi注意shared那列8个G。这说明有大量共享内存或tmpfs文件占着内存它们挂在buff/cache里但内核不能像回收普通文件页那样把它们说扔就扔。所以系统明明显示cache有12G实际可用的available只有2.2G。这种场景才是需要去排查和处理的。这也是我在下一章想展开的重点哪些“高cache”是需要管的哪些是不用管的。3. 真正会导致问题的三种“假cache”3.1 脏页刷不回磁盘正常业务里最典型的“cache高但需要处理”场景是脏页堆积。先讲概念当进程往文件里写入数据时数据首先进入page cache成为“脏页”dirty page。脏页会由内核的写回线程现代内核里是flusher threads在合适的时候写回磁盘写成功后就不再是脏页可以随时释放。问题出在写回速度跟不上写入速度的时候。假设你有一台机器在做批量数据导入程序以几百MB/s的速度往磁盘写文件而磁盘落盘速度只有100MB/s那么内存里的脏页就会越积越多。在/proc/meminfo里表现为Dirty数值持续上涨可高达好几个G。此时再从free看buff/cache高、available低。如果在脏页达到一定比例后内核触发“强制同步写回”正在执行写的进程会被阻塞体感就是程序写文件卡顿了一下严重的时候整机I/O卡死。这类问题的直接原因往往不是“内存不够”而是“内存成了写缓冲区的蓄水池蓄水速度超过泄洪速度”。排查方向是确认磁盘写性能、应用程序的写入模式而不是简单地清内存缓存。3.2 tmpfs和shmem撑起来的“伪缓存”这是一个很容易被忽视的大坑。Linux里有一种特殊文件系统叫 tmpfs它看起来是一个目录实际数据全部存在内存里。常见挂载点有/dev/shm、/run、部分发行版的/tmp还有容器场景里大量使用的内存盘和共享内存段。当有程序往/dev/shm里写文件或者用POSIX共享内存、System V共享内存分配内存时对应的内存会被计入Shmem同时也在buff/cache里体现。问题在于tmpfs文件不会被内核自动回收。只要你没删掉那个文件它就永远占着内存。很多服务比如某些Java应用、消息中间件、数据处理任务喜欢往/dev/shm写临时文件跑完忘了清理内存就被“伪cache”给吃了。这也是我在4.2节中用free看共享内存列能发现问题的原因。处理办法说起来也简单找到占用共享内存的进程清掉或停掉再删掉对应的tmpfs文件内存立刻回来。难的是定位命令后面章节会讲。3.3 被删文件占着位置不放还有一个比较隐蔽的场景某个进程打开了一个大文件在运行期间这个文件被删掉了比如日志轮转、临时文件自动清理但进程的句柄还开着。此时这个文件占用的page cache不会被内核释放因为内核认为还有进程在用这些数据。结果就是磁盘上文件没了内存里的缓存却还在buff/cache居高不下而且不好释放。这种场景排查时有一个经典命令lsof L1 | grep deleted | sort -k7 -rn | head -20它的作用是列出所有“已删除但仍被占用”的文件按大小倒序排。看到那些几百M甚至几个G的deleted文件基本可以锁定罪魁祸首了。找到进程后要么重启该进程释放句柄要么协调业务方把临时文件的管理策略改掉。注意这个命令需要一定权限很多系统文件只能看到自己的排障时要确保是root用户容器里看不到宿主机其他进程需要到宿主机层面执行。4. 手动清理的正确姿势4.1 drop_caches到底该怎么用先明确一点正常运维不建议没事就去清缓存。page cache对文件读取性能非常重要你把它清了下次读文件又要重新走磁盘I/O系统整体性能反而会下降。但有些场景确实需要手动释放比如刚跑完一个超级耗内存的批量任务内存里攒了大量一次性缓存而紧接着要启动一个大内存服务这个时候临时清一次是合理的。手动清理的命令是往/proc/sys/vm/drop_caches写值# 先同步把脏页刷回磁盘 sync # 清理页缓存page cache echo 1 /proc/sys/vm/drop_caches # 清理slab中的可回收部分主要是dentry/inode缓存 echo 2 /proc/sys/vm/drop_caches # 清理以上两者 echo 3 /proc/sys/vm/drop_caches写入1、2、3分别对应不同清理范围。写入之后这个文件的值一般又会自动变为0这是正常现象表示清理过程已经触发完毕。需要特别强调的是drop_caches不会释放共享内存/tmpfs的占用也不会把不可回收的slab清掉。所以如果前面说的“假cache”场景占了主要内存调用这个命令基本没用。在生产环境执行前一定先确认系统没有大量脏页否则建议先sync。虽然drop_caches不会直接丢数据但干净地落盘总是更稳妥。历史上我见过有同事不执行sync直接echo 3 当时没什么问题但原理上这等于在内存里还有未落盘数据时强行清理缓存风险没必要冒。4.2 通过内核参数让写回更积极如果是脏页堆积导致的问题清缓存只能治标治本要看内核参数。核心是/proc/sys/vm/dirty_background_ratio和/proc/sys/vm/dirty_ratio。dirty_background_ratio当脏页占内存的比例超过这个阈值内核的后台写回线程开始把脏页刷到磁盘。默认常见值是10%比如16G内存大概1.6G脏页时开始后台刷。这个过程的特征是异步的不太影响应用写文件。dirty_ratio当脏页占比超过这个阈值用户进程自己的写入操作会被阻塞必须同步参与写回。默认值在不同内核里有差异常见是20%~30%。这个过程是同步的写文件的应用能明显体感“卡一下”。所以当你看监控发现Dirty长期维持在很高水平、业务写文件时出现周期性卡顿正确思路是调低dirty_background_ratio让内核更早、更频繁地在后台把数据落盘尽量避免冲到dirty_ratio触发同步写回。临时调法sysctl -w vm.dirty_background_ratio5 sysctl -w vm.dirty_ratio15想永久生效写到/etc/sysctl.d/99-dirty.confvm.dirty_background_ratio 5 vm.dirty_ratio 15然后执行sysctl --system加载。这里有个注意点内存特别大的机器比如几百G内存百分比会放大成一个很大的绝对值容易造成脏页积累过多。针对大内存场景更推荐用字节上限来控制比如设vm.dirty_background_bytes268435456256MB这样阈值不会因为总内存大而上浮。注意dirty_background_bytes和dirty_background_ratio是二选一的关系设置了 bytes 后 ratio 会自动失效二者不要同时设置。调整的时候要克制别把dirty_background_ratio调得太低比如调到1%那样内核会频繁刷盘磁盘I/O压力反而增大写密集业务吞吐会掉。我建议从5%起步观察。4.3 从应用层把缓存问题“消灭在源头”内核参数只能调整回收行为真正稳妥的解法是在应用层控制内存的“制造量”。举几个我在实践中见过的高频例子有人写日志或数据导出脚本用单个进程无限往磁盘写大文件写几天内存就吃紧。解决方案是尽量走标准库/系统日志轮转机制限制单文件大小让老数据及时关闭、删除。有人用Java服务做批量计算把大量临时文件写到/dev/shm或tmpfs目录完成任务后没有清理。解决方案是写任务时增加finally清理逻辑或改用磁盘目录。数据库这类服务通常有自己管理的buffer pool比如InnoDB的Buffer Pool就是自己控制内存不走page cache的自动回收。但数据库的redo log写盘、排序临时文件仍然会用到page cache。这类核心服务所在的机器不要手动清cache否则很容易让数据库性能体验突然下降。从架构角度看如果某个服务的临时文件注定很大更合理的做法是给它单独挂一块磁盘目录而不是依赖tmpfs。内存再便宜也不是拿来装临时文件的。5. 一个完整排查案例文件服务器可用内存告急5.1 现场症状和第一轮排查我处理过一台文件下载服务现象是监控面板上buff/cache占了将近12GMemAvailable只剩几百M页面访问偶发卡顿。第一反应当然是先看free -h$ free -h total used free shared buff/cache available Mem: 15Gi 2.1Gi 800Mi 8.0Gi 12Gi 2.2Gi Swap: 2.0Gi 0B 2.0Gi注意shared那列直接8个G这个信号非常明显可以初步怀疑共享内存或者tmpfs占了大量空间。继续看/proc/meminfo的关键字段Shmem: 8388608 kB Dirty: 0 kB SReclaimable: 123456 kB SUnreclaim: 23456 kBDirty几乎为零说明不是脏页堆积Shmem8G问题大概率就出在这了。再用df -h | grep tmpfs看一眼挂载情况$ df -h | grep tmpfs tmpfs 7.8G 8.0G 0G /dev/shm/dev/shm 基本被塞满了这说明肯定有进程往里面写过大量数据而且没清理。5.2 定位到真正的元凶知道共享内存盘被塞满下一步就是找谁在写。两个命令搭配着用。先看/dev/shm下的文件$ ls -lahR /dev/shm/ total 8.0G drwxr-xr-x 2 root root 4096 ... -rw------- 1 java java 8.0G ... some-java-service.tmp很好一个Java服务往/dev/shm写了一个8G的临时文件而且还没删。接着确认到底是哪个进程$ lsof /dev/shm/some-java-service.tmp COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME java 12345 java mem REG 0,21 8589934592 123 /dev/shm/some-java-service.tmp到这里基本就水落石出了。这个Java服务在运行期间申请了8G的共享内存用完之后没有主动清理加上进程还在运行所以/dev/shm里的文件一直占着内存。由于tmpfs不在内核自动回收的范围内显示出来的就是“cache高、available低”的假象。5.3 落地解法与效果处理方案分成两步。第一步是止血停掉异常服务删掉临时文件内存立刻就能释放不少。如果业务要求服务不能停可以协调业务方在下次重启时优化资源申请逻辑或者在运行时增加临时文件清理操作。第二步是长效措施如果该业务确实需要大块共享内存建议把tmpfs挂载点改大或者改用磁盘目录防止它把系统内存空间吃死。我当时的处理是先让服务方清理了临时文件然后再监控内存指标available很快就恢复到了10G以上页面卡顿也随之消失。这个案例之所以典型是因为它完美解释了“为什么缓存高不见得是缓存本身的问题”。单纯看buff/cache高不去区分Shmem和普通page cache很容易把解决问题的方向带偏。6. 常见误区、高频问题与避坑速查6.1 关于cache清理的几个错误认知在社区里看到很多人讨论“Linux内存占用过高”经常能看到一些被当成万灵丹的办法实际并不可取。第一个误区是“把 cache 清了就万事大吉”。实际上如果系统里有大量的dirty页或者Shmem占大头drop_caches根本没有效果。即便能清掉过几分钟cache又会涨回来因为业务还在读写文件内核天然会重新建立缓存。你只是把一个“正在被利用的资源”变成了“空闲资源”系统整体没赚到任何东西。第二个误区是“cache占用高就该加内存”。这个结论要分场景。如果available长期很低、而且定位到是业务缓存tmpfs、共享内存不可回收导致的加内存可以缓解但如果只是普通page cache高说明文件读取频率高加内存反而会让cache变得更大不一定解决根本问题。该加速的是I/O瓶颈不是内存总量。第三个误区是“所有缓存都是洪水猛兽”。对于文件服务器、数据库、Web服务来说page cache恰恰是性能保障。把它清零意味着热数据的读取全都要重新走磁盘业务响应时间会明显恶化。我之前在一台nginx文件下载机器上做过测试清完cache后首次下载延迟上涨了几倍后续热度恢复后才慢慢回落。所以在没有明确问题前真的别手痒。6.2 别把系统锁文件报错误认为内存缓存问题在搜索“Linux cache 占用高”时经常会碰到另一个完全不相关的问题waiting for cache lock: could not get lock /var/lib/dpkg/lock-frontend。这是Debian/Ubuntu系统上apt或dpkg的锁文件冲突含义是“有一个软件包管理操作正在运行其他操作必须排队等待”跟内存缓存没有半毛钱关系。如果你是在Ubuntu上执行安装命令时看到这个报错正确排查思路是看后台是不是有apt、unattended-upgrades之类的进程在跑。等它跑完或者必要时确认没有安装任务后清理锁文件而不是去研究内存cache。这个点放在这里提醒一下是因为实在太容易搜混了两个东西都叫“cache”一个是内存管理概念一个是文件锁千万别张冠李戴。6.3 速查表什么情况该动、什么情况不该动下面这个表是根据我日常经验整理的判断参考可以直接收藏备用症状判断结果该做什么buff/cache高available充足健康状态内核拿空闲内存做Cache不用管别清缓存buff/cache高available低Dirty持续高脏页堆积写回跟不上调低dirty_background_ratio排查磁盘写性能必要时适量调低dirty_ratiobuff/cache高available低Shmem高tmpfs/共享内存塞满了定位 /dev/shm、/run 等tmpfs挂载点清理临时文件或调整应用逻辑buff/cache高available低发现有大量deleted文件文件被删但句柄未释放lsof L1定位进程重启进程或协调业务方释放句柄刚跑完一次大任务内存里都是冷Cache临时高cache业务即将需要大内存先sync再echo 3 /proc/sys/vm/drop_caches不建议常驻定时执行再补充一个排查命令集合一次性把关键信息看全free -h cat /proc/meminfo | grep -E ^(MemTotal|MemAvailable|Dirty|Writeback|Shmem|Slab|SReclaimable|SUnreclaim) df -h | grep tmpfs lsof L1 | grep deleted | sort -k7 -rn | head -20这几条命令基本能覆盖绝大多数“cache高”的定位需求。最后再分享一个我的个人感受。在Linux上“内存占用高”是一个极其容易被误解的信号因为内核的设计目标本来就是把内存物尽其用。排查这类问题最重要的不是学习怎么一键清缓存而是学会区分“可回收缓存”和“不可回收内存”。前者高是常态后者高才是故障。搞清楚这个区别你就能在告警面前保持镇定也能在真正出问题的时候一击即中。反正我现在的习惯是先在/proc/meminfo里把Dirty、Shmem、SReclaimable这几个字段扫一眼再决定下一步往哪走。这个顺序帮我避开过很多次无效操作也算是在反复踩坑之后沉淀下来的一个笨办法希望能帮你少走点弯路。
返回列表