核心本质:页缓存是Linux内核在内存里开辟的一块缓冲区,用来缓存磁盘上的数据块。它的目标是化解低速磁盘和高速内存之间巨大的速度鸿沟,所有读写文件的系统调用,默认都会经过页缓存。

一、底层原理拆解

磁盘IO速度极慢,内存速度比磁盘快上万倍。操作系统不会用户每次read/write就直接读写物理磁盘。
当进程发起读文件:

  1. 内核先去页缓存查找目标数据对应的内存页;
  2. 如果命中,直接把内存里的数据拷贝给应用程序,不碰磁盘,这就是缓存命中;
  3. 如果没有命中(缓存未命中/缺页),内核发起磁盘IO,把磁盘对应块读到内存页缓存中,再把数据交给应用,后续再次读取同一块数据,直接走缓存。

写文件流程:

  1. 应用调用write,数据并不会直接写到硬盘,而是写入页缓存;
  2. 此时页缓存里的页面标记为脏页(dirty page),代表内存中的数据和磁盘不一致;
  3. 内核后台有pdflush线程,会在合适时机,把脏页异步刷入物理磁盘;
  4. 刷盘完成后,脏页标记清除,内存页和磁盘内容保持一致。

重点:你前面Redis AOF里的fsync,就是强制把脏页从页缓存刷到磁盘,绕过操作系统后台的自动刷盘策略。write仅仅写到页缓存就返回,fsync才真正落地到硬件磁盘。

二、页缓存的内存来源

页缓存占用的内存,属于系统空闲内存。Linux的设计哲学:空闲内存不能闲置,尽量拿来做缓存提升IO性能。
当应用程序需要申请新内存时,如果页缓存占用较多,内核会回收缓存页面,释放内存给进程使用。
所以在Linux服务器看free命令,看到cached数值很大,不是内存泄露,是系统在用内存加速磁盘读写。

三、脏页的刷盘机制

脏页不会永久留在内存,内核有一套阈值控制自动刷盘:

  1. 当脏页总量达到系统上限,触发主动刷盘;
  2. 脏页存活时间超过阈值,pdflush线程会将超时脏页刷盘;
  3. 系统关机、卸载磁盘时,强制刷所有脏页。

这里就解释AOF的三个appendfsync选项:

  1. appendfsync no:Redis只write写入页缓存,不调用fsync,交给内核pdflush后台刷盘。断电时,内存页缓存中还没刷盘的脏数据直接丢失。
  2. appendfsync everysec:Redis每秒调用一次fsync,强制把AOF文件对应的脏页刷到磁盘。最多丢失1秒内写请求。
  3. appendfsync always:每一条写命令执行完成,立刻调用fsync,强制刷脏页到磁盘,几乎不丢数据,但磁盘IO压力巨大。

四、关键区分:页缓存 vs 应用层缓存

很多人混淆,这里拆清楚边界:

  • 页缓存:内核层面,操作系统自带,对应用程序透明。不管PHP、Redis、Nginx,只要读写文件,自动走页缓存,应用代码不用写任何逻辑。缓存对象是磁盘文件块。
  • Redis缓存:应用层缓存,用户态程序自己维护。缓存对象是业务key-value,数据直接放在Redis进程内存,不走文件、不走页缓存。

五、业务开发里的坑点

  1. write系统调用成功 ≠ 数据落盘。只是写入页缓存,断电依然丢数据,这是最经典误区。想要持久化安全,必须fsync。
  2. 服务器内存充足时,大量文件读请求几乎全部命中页缓存,磁盘IO很低;内存不足,页缓存频繁回收,大量缺页,磁盘压力飙升,性能暴跌。
  3. 大文件顺序读写,页缓存效果极好;随机读写,缓存命中率会下降。
  4. 页缓存是按内存页(4KB默认)为单位管理,不是按文件一行或者业务数据。

六、和Redis持久化联动总结

AOF日志本质就是普通磁盘文件,Redis写AOF,就是调用write写入页缓存。appendfsync控制什么时候调用fsync把脏页强制刷入磁盘。
RDB生成rdb文件,同样经过页缓存,BGSAVE写完rdb文件后,内部也会执行fsync,保证快照文件落地磁盘。

Buffer Cache(块缓冲):早期独立,新版Linux已经合并进Page Cache。Buffer用于缓存块设备裸块,Page Cache用于缓存文件。现代Linux统一为页缓存。

mmap内存映射:mmap就是把磁盘文件映射到进程虚拟内存,读写mmap内存区域,底层操作的依旧是页缓存,脏页依旧由内核异步刷盘。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐