Linux页缓存(Page Cache)原理、运维监控与性能调优实战

发布时间:2026/10/24 5:49:20
Linux页缓存(Page Cache)原理、运维监控与性能调优实战 1. 一次线上卡顿排查让我重新审视页缓存前阵子某公司的一套核心业务服务出现了一种很诡异的“慢”CPU使用率只有十几个百分点磁盘IO也不高网络带宽更是宽松得很可用户就是反馈接口响应变慢而且慢得很有规律——每次服务重启或者新扩容的机器上线后的头几十分钟内特别明显跑一段时间又恢复正常。刚开始怀疑是应用代码的问题把JVM堆、GC日志翻了个底朝天也没找到明显异常。后来用atop和pidstat盯着看了半天才注意到一个此前一直被我忽略的指标page fault缺页异常数量高得离谱同时read()系统调用的耗时在微秒级和毫秒级之间反复横跳。问题的根子其实不在应用层而在操作系统最基础的一个机制上——页缓存Page Cache。那次之后我把系统存储这块重新梳理了一遍也踩了不少坑。这篇就把页缓存的原理、运维观察点和几个容易误判的实战场景一次性讲清楚希望能帮后来的人少走弯路。1.1 那次故障的表象与定位过程先说当时的排查链路方便你理解页缓存在里面扮演的角色。服务启动时加载的配置、依赖的模板文件、频繁读取的热点数据这些在进程刚起来的时候都不在内存里。每次read()请求落到文件系统上如果数据不在页缓存中内核就得发起真正的磁盘IO去读盘这就是一次major fault主缺页。磁盘IO的延迟通常在几毫秒到几十毫秒而命中页缓存之后的read()只需要在内存里拷贝数据耗时骤降到微秒级。两种量级的落差反映到接口上就是“时快时慢”的抖动。我当时的定位过程很简单先用sar -B看pgfault/s和pgmajfault/s再用strace -c统计系统调用耗时最后用fio单独测了一下磁盘裸IOPS和延迟确认硬件本身没问题。这一整套下来矛头就指向了页缓存的冷热命中问题。注意如果线上服务出现过“重启后变慢、跑一阵子又变好”的现象优先查页缓存命中率和主缺页数这比查应用代码效率高得多。1.2 页缓存究竟在系统里扮演什么角色页缓存本质上是内核用内存里空闲的物理页去缓存磁盘上的文件数据。Linux 设计它的核心出发点就一句话磁盘太慢了内存相对快好几个数量级那就把最近读写过的磁盘数据留在内存里。这里的“缓存”不是某个独立模块而是内核地址空间里一系列页的集合。当你打开一个文件去读内核会先检查这些页在不在页缓存里在的话直接返回不在的话从磁盘读出并放入页缓存下次再读同一份数据就能直接命中。这套机制对数据库、Web服务、文件服务器这类读多写少的负载尤其重要。你可能听过“Linux会把空闲内存用满”的说法——这不完全是调侃只要有过文件读写那些被读过的页就会自然地留在页缓存里。所以看到free命令显示可用内存很少时先别急着慌里面往往有很大一部分就是页缓存。2. 页缓存的工作链路读、写、命中的完整路径要运维好页缓存光知道“它是个缓存”远远不够。你得理解读路径和写路径分别经历了什么这样后面碰到缓存失效、脏页堆积、内存回收这些问题时才能顺着链路去排查。2.1 读路径read() 和 Page Fault 之间发生了什么用户程序调用read()后并不是直接去磁盘拿数据的。完整流程大致是系统调用进入内核VFS 层根据文件路径定位到对应的 inode。通过 inode 上维护的地址空间address_space去查找页缓存看目标数据所在的页是否已经在内存里。如果页不在内核会触发缺页异常分配一个新页发起磁盘IO把数据从文件系统读入这个页。数据到达后内核把页插入页缓存树再拷贝到用户空间的缓冲区read()返回。这里面有个关键点页缓存的最小单位是一个页通常是 4KB。也就是说就算你只读 1 字节内核也可能把整个 4KB 的页从磁盘读上来。如果程序是顺序读大文件这个机制非常高效因为读一页的同时内核还会做预读readahead——提前把后面几页也读入缓存。预读是页缓存性能好的重要原因之一。拿读日志文件举例日志是典型的顺序追加写、顺序读预读机制会让连续读取的效率接近内存拷贝速度。反过来如果是随机读小文件预读效果就大打折扣因为读进来的页很可能后面根本用不上白白占着内存。2.2 写路径先落内存后台再慢慢同步写路径比读路径更反直觉。很多人以为write()返回就是数据写进磁盘了其实早期Linux并不是这样。write()的常规路径是如果目标页不在缓存中先分配一个页并读入旧内容部分写场景需要保留未修改部分。在页上做修改把页标记为脏页dirty page。write()直接返回成功数据此时只存在于内存。磁盘落盘由内核的回写机制writeback在后面异步完成。这样做的好处是写性能极高因为系统调用不需要等待磁盘IO坏处是如果发生断电或宕机那些还在内存里的脏页会直接丢失。你可能会说现在很多文件系统不是开了O_SYNC或者fdatasync吗对应用层可以主动要求同步写但代价就是每次写都要等磁盘IO性能断崖式下跌。数据库这类对持久性要求极高的软件会自己控制刷盘时机而普通文件读写默认走的就是异步回写这条路。需要特别说明写路径的这种延迟落盘行为正是后面讲脏页参数调优的背景。理解了“write 先落内存再异步刷盘”你才能搞清楚为什么内存里的脏页会越积越多。2.3 缓存命中率为什么是核心指标运维页缓存时命中率hit ratio是所有调优的基石。命中率 命中的读请求数 / 总读请求数。命中率越高说明越多的读请求没有触碰磁盘系统响应速度就越接近纯内存访问。观测命中率不需要什么高端工具/proc/vmstat里有几个关键计数器pgpgin从磁盘读入页缓存的页数。pgpgout从页缓存写出到磁盘的页数。pgfault缺页异常总数包括主缺页和次缺页。pgmajfault主缺页次数即真正需要磁盘IO服务的缺页。两个采样点之间(pgfault - pgmajfault) / pgfault大约就是次缺页命中比例这个数如果偏低说明大量读请求都在等磁盘。我还习惯配合/proc/pressure/memory来观察内存压力里面some和full两列分别表示部分任务和全部任务因内存不足而等待的时间比例。如果full长期不为零页缓存的回收可能已经拖累了业务。3. 看懂 free 和 /proc/meminfo运维天天看却常常误解的数据页缓存运维里最常见的翻车现场不是参数调错而是看不懂数据。一个free -m就能让不少人产生“内存不够了”的误判。3.1 buff/cache 和 available 的真实含义先看一个典型输出$ free -m total used free shared buff/cache available Mem: 15961 784 2243 856 12933 14921 Swap: 2047 0 2047总共 16GB 内存used只有 784MBbuff/cache占了 12.9GB。但注意available是 14.9GB并没有告警。这里的门道在buff/cache上buffbuffer对应块设备上的缓冲比如直接读写裸盘、文件系统元数据操作。cachecache大部分就是页缓存即文件数据的内存副本。这两个数值计入“已用”还是“可用”取决于内存压力。available的估算方式是剩余空闲内存加上可回收的页缓存部分——因为当进程申请内存时内核优先回收干净的页缓存来满足请求而不是把进程挤出去。所以used列的参考价值很有限。一个内存长期占用 80% 以上的机器如果大头都在buff/cache说明文件访问活跃系统反而可能运行得很好。3.2 active、inactive、file 与 anon回收优先级是怎么排的/proc/meminfo里那串字段才是硬核运维要关注的东西。核心是看懂几个概念Active活跃页最近被访问过的页回收优先级低。Inactive非活跃页一段时间未被访问回收优先级高。File文件页对应文件数据的页缓存干净页可随时回收。Anon匿名页进程堆、栈这类不关联文件的内存回收时需要先交换到磁盘。理解这套分类非常重要。内存在紧张时会先回收Inactive(file)——因为这类页丢弃后还能从磁盘读回来损失只是重新读一次的IO然后是Active(file)最后才考虑匿名页的交换。一条非常实用的经验是当Inactive(file)持续偏低而Active(file)居高不下时说明缓存整体偏向热点数据命中率通常不错反过来如果大量文件页都堆在Inactive里说明访问模式比较分散或者有大批一次性读取页缓存占用虽高但命中的收益有限。举例来说用dd一次性拷贝 50GB 文件后再free看内存你会看到cache高得吓人但这些页属于“一次性页”下次没有任何程序会再读它们纯属占着位置。这种情况下的高缓存占用就不值得高兴了。3.3 内存压力事件pswpin/pswpout 才是警报器我在判断一台机器“是不是真的内存不够”时从不过度依赖free剩余空间而是看/proc/vmstat的pswpin和pswpout这两个字段统计从交换区换入和换出到交换区的页数。如果它们持续增长说明系统已经在大量使用Swap这是真正的内存压力信号。页缓存再多只要它能被顺利回收满足新请求就不算真正的压力源但一旦触发Swap进程的访问延迟会上升到几十毫秒甚至更高这时候就需要介入处理了。4. 脏页回写机制你以为写完了其实数据还在内存里前面提到write()先写内存、异步落盘。这个异步过程受内核参数控制调不好会出现两种极端要么脏页堆积太多导致内存压力要么频繁刷盘拖垮IO性能。4.1 四个关键参数各自管辖什么运维中最常打交道的四个参数是参数默认值作用dirty_expire_centisecs300030秒脏页在内存里最多停留多久超过后必须写回dirty_writeback_centisecs5005秒后台回写线程多久唤醒一次dirty_background_ratio10脏页占内存比例达到该值后台开始回写dirty_ratio20脏页占内存比例达到该值进程的自己写操作开始同步等刷盘理解这四个参数的配合逻辑回写线程是周期“扫荡”的到点就把到期脏页刷下去同时还有水位控制——脏页占比超过dirty_background_ratio时后台线程开始工作超过dirty_ratio时连写文件的进程都得停下来帮忙刷。这里特别强调一个误区把dirty_background_ratio调高确实能让写操作更快返回但代价是丢数据的风险窗口变大、内存里可回收的干净页变少。如果用到掉电保护要求高的数据库场景这个参数必须谨慎。4.2 一台机器脏页暴涨实际排查过程有一回某台文件服务器表现出来的是内存用了很多但free显示cache并不高系统负载却不低。cat /proc/meminfo一看Dirty字段高达 2.3GB正常情况应该在几十到几百MB。排查时我按这个顺序走的先看dirty_background_ratio和dirty_ratio是否被改过——没有默认值。看dirty_expire_centisecs——也是默认的30秒。再确认磁盘IO能力。用iostat -x 1看%util结果接近100%磁盘写带宽已经打满。真相是回写线程每5秒唤醒一次但磁盘处理能力有限新产生的脏页速度大于刷盘速度Dirty值自然只升不降。这不是参数配置问题而是硬件瓶颈。那类IO密集型场景可以做的优化是调低dirty_background_ratio比如从10降到2让回写提前介入避免脏页囤积或者使用延迟更低的存储设备。反过来在SSD上由于随机写性能强回写效率高脏页一般不容易堆积。经验上的判断是Dirty 长期超过内存总量的5%就要警惕超过10%还伴随IO近饱和就需要立刻看存储侧瓶颈。5. 缓存回收与 drop_caches这个操作别乱用页缓存既然是“缓存”内存紧张时就得给进程腾地方。腾地方的机制有两套运维日常接触最多的是主动清理命令drop_caches。5.1 kswapd 和直接回收内存不够时系统会怎么选Linux 内存回收有两个触发路径后台回收kswapd每个NUMA节点都有一个内核线程当空闲页低于watermark低位时被唤醒异步回收页缓存。直接回收direct reclaim进程申请内存时发现空闲页不足被迫同步回收此时进程会阻塞在内存分配上。这两条路线的差异对业务的体感影响巨大。后台回收不阻塞进程一般无感直接回收发生在分配路径上会直接让业务线程卡住表现为应用“莫名其妙的卡顿”。dmesg里如果能看到大量的内存分配延迟告警大概率就是直接回收在频繁发生。页缓存在这套机制里的角色是“缓冲垫”它占着内存但在压力来临时可以被快速回收帮助系统避免直接回收。所以有一种看似矛盾的现象进程明明申请不到内存free却显示cache还有几个GB——这不是bug而是内核正在回收这些缓存页回收需要时间导致进程短暂等待。5.2 drop_caches 的正确使用姿势和常见的坑运维圈流传过一句“内存不够就 echo 3 /proc/sys/vm/drop_caches”这话半对半错。drop_caches只能释放干净的页缓存也就是没有被修改过的文件页脏页需要先写回才能释放。数值的含义1释放页缓存。2释放目录项和inode缓存dentries和inodes。3释放页缓存、目录项和inode缓存。看起来简单但我在生产环境见过太多滥用第一个坑释放后短期内命中率暴跌。你把几个GB的页缓存丢掉业务还在继续读同一批文件内核不得不重新从磁盘读入瞬间IO压力飙升接口延迟不降反升。所以drop_caches只适合两种情况——刚完成一次性大文件处理的机器、或者内存确实紧张且确认缓存命中率很低。第二个坑在业务高峰期执行。假设数据库正在频繁读某张表你执行了echo 3缓存全清下一次查询全部走磁盘这等于自己制造了一次性能灾难。第三个坑以为能解决所有内存问题。如果内存压力来自匿名页应用本身占用的堆、栈drop_caches毫无用处。这时候需要排查的是进程内存泄漏或配置不合理。我的习惯做法是先看/proc/meminfo里的Dirty值如果脏页不多且Active(file)占比不高再考虑是否清理。否则干脆不动让内核自己管。5.3 怎么判断缓存该不该清理与其讨论怎么清理不如建立一个判断框架。我一般从三个问题入手这里面的页未来短期内会被再次读取吗会——别清不会——清理收益大。当前磁盘IO压力如何如果iostat显示写压力高清缓存会让读请求重新落盘雪上加霜。清掉之后能马上解决什么问题仅仅为了让free显示更多可用内存这没有实际意义。内核里的页缓存本来就可以被回收清理只是提前释放不改变化势。6. 页缓存运维的几条实际经验理论和机制讲完结合我自己多年运维和性能调优的实践这部分挑几条最容易被忽视、但价值很高的经验总结给你。6.1 判断缓存是否需要介入的几个先决问题我在任何内存调优前必看这几个数据缺一不可/proc/meminfo里的Dirty、Active(file)、Inactive(file)、Shmem。/proc/vmstat里的pgpgin、pgpgout、pswpin、pswpout。iostat -x 1的%util、r/s、w/s、await。应用的接口耗时分位数尤其是P99。把这个组合摸清楚基本就能判断是“缓存太大需要处理”还是“缓存太小命中不足”。举个例子如果pgpgin很高、%util很高、P99很高那说明缓存命中不够业务读取频繁落盘这时该做的是扩容内存或优化读取方式而不是清理缓存。反过来如果pgpgin很低、Dirty很高、写IO打满需要优化的是回写参数和存储性能跟页缓存占用没有直接关系。6.2 容器和云主机的场景里要额外留意的点容器环境下页缓存的表现和物理机不太一样几个注意点Page Cache 属于内核态多个容器共享同一宿主机的页缓存池。如果一个容器疯狂读文件占用了大量页缓存另一个容器申请内存时可能会触发宿主机全局回收导致SLA被“无辜邻居”影响。这在混部场景里极其常见。容器内存限制cgroup limit生效时容器内的页缓存超过限额会被强制回收这会导致容器内读文件变慢但你在物理机上看到的free可能一切正常。云主机如果开启了内存超卖overcommit页缓存释放速度会比普通物理机更慢因为宿主机需要时间把内存物理归还给超卖池。在这些场景下我的建议是给容器内的应用设置合理的内存limit并在监控大盘里同时看宿主机和容器两层指标不要单看某一层。6.3 实操下来最值得记住的几条结论最后分享几条我踩过坑之后沉淀下来的结论你拿过去直接用问题不大free显示内存吃满不重要观察 available、Dirty、pswpin/pswpout 才重要。大多数“内存不足”其实是可回收页缓存过多宁可让内核自行回收也不要轻易在业务高峰期drop_caches。写多场景下脏页回写参数默认值足够合理真正要盯的是磁盘IO写入能力参数再调也改变不了硬件上限。容器里业务出现间歇卡顿先查是否因为 cache 回收引起 direct reclaim再考虑调整 cgroup 配置或CPU/内存分配。除非有明确依据否则不要在生产环境为了腾内存而去清缓存清理带来的IO放大通常比内存收益更危险。页缓存看似是Linux里一个默默无闻的底层机制但几乎所有线上文件的读写性能都绕不开它。把free、meminfo、vmstat这些基础工具读明白了再遇到“系统内存很高但业务很慢”这类问题处理起来会从容得多。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询