操作系统缓存原理与实战:从Page Cache到Redis的缓存分层策略 📅 2026/7/1 1:18:18 1. 先搞清楚“操作系统缓存”到底在解决什么问题很多人一提到缓存第一反应就是 Redis、Memcached 这类中间件。这没错它们是解决应用层数据共享和加速的利器。但如果你只盯着它们可能会忽略一个更底层、更普遍、且几乎零成本的缓存机制——操作系统缓存。这篇文章不是要你放弃 Redis而是想让你明白在考虑引入任何外部缓存之前你的服务器操作系统可能已经默默帮你缓存了海量数据。理解它能帮你避免很多“伪性能问题”比如明明 Redis 响应是微秒级但整个请求还是慢。数据库查询已经优化了索引但重复查询相同数据磁盘 I/O 依然很高。总感觉系统“内存没用完”但性能就是上不去。操作系统缓存的核心价值在于利用空闲内存来加速磁盘 I/O。当你读取一个文件或者数据库从磁盘加载数据页时操作系统不会在用完后立即丢弃这些数据。只要内存还有富余它就会把这些数据留在内存中。下次再需要访问相同数据时直接从内存读取速度比从磁盘读取快几个数量级。这个过程对应用程序是完全透明的你不需要写一行代码。所以这篇文章适合两类人看正在为系统 I/O 瓶颈、数据库负载高而头疼的开发者或运维。想深入理解计算机系统工作原理不满足于只会用工具的人。最关键的一点是学会观察和利用操作系统缓存是进行有效性能分析和容量规划的基础。它能告诉你你的内存是否被有效利用你的磁盘压力是否真实存在以及你加的 Redis 缓存到底是在弥补哪一层的缺陷。2. 操作系统缓存是如何工作的不只是“读缓存”很多人把操作系统缓存简单理解为“读缓存”这不够准确。为了高效管理现代操作系统如 Linux、Windows的缓存机制要复杂得多主要涉及以下几个核心部分2.1 Page Cache最重要的磁盘缓存层这是操作系统缓存的主力。当数据从磁盘读入内存或从内存写回磁盘时都是以“页”为单位进行管理的。Page Cache 就是这些内存页的缓存。读加速应用程序请求读取文件 A 的某部分。内核先检查这部分数据是否在 Page Cache 中。如果在缓存命中直接返回零磁盘 I/O如果不在缓存未命中则发起磁盘 I/O读入数据并放入 Page Cache 以备后用。写缓冲应用程序写入文件 B。数据通常不会立即刷到磁盘而是先写入 Page Cache标记为“脏页”。内核会在后台由pdflush等线程根据一定策略如脏页比例、时间间隔将数据异步写入磁盘。这被称为“回写缓存”能极大提升写入性能但需要注意数据一致性和掉电风险。预读当系统检测到你在顺序读取文件时比如读取一个大日志文件它会“聪明地”预读后续的数据块到 Page Cache 中让你接下来的读操作直接命中缓存。如何观察在 Linux 下free -h或cat /proc/meminfo命令中cached这一项就大致代表了 Page Cache 的大小。它和buffers缓冲常用于元数据等一起构成了系统利用的磁盘缓存内存。$ free -h total used free shared buff/cache available Mem: 7.6G 2.1G 1.2G 200M 4.3G 5.0G Swap: 2.0G 0B 2.0G这里的buff/cache(4.3G) 就是已被使用的缓存和缓冲区内存。注意available(5.0G) 列它估算的是可用于启动新应用的内存包含了可回收的 Cache。2.2 Buffer Cache 与 Page Cache 的演进在早期的 Linux 内核中Buffer Cache块设备缓存和 Page Cache文件缓存是分开的这可能导致同一份数据在内存中有两份拷贝。现代内核已经将它们统一现在我们通常提到的“Page Cache”已经包含了文件数据和元数据的缓存。free命令中的buffers现在更多指代原始块设备的元数据缓存等。2.3 交换机制Swap与缓存的关系这是容易混淆的点。当系统物理内存紧张时内核需要回收内存页。回收顺序通常是先回收干净未修改的 Page Cache因为它们可以直接丢弃需要时再从磁盘读即可如果还不够则会尝试将不活跃的“脏页”写回磁盘后回收最后的手段才是将一些暂时不用的进程内存交换到 Swap 分区磁盘空间。关键经验一个健康运行的服务器的内存使用应该是“满”的但 Swap 使用应该很少或为零。“满”的内存意味着大量的 Page Cache这是好事说明内存被充分利用来加速 I/O。而 Swap 被频繁使用si/so值高则通常是物理内存真的不足了会引发严重性能问题。2.4 文件系统层级的缓存除了内核管理的 Page Cache一些文件系统如 ext4, xfs或分布式文件系统客户端会有自己的元数据缓存如 inode cache, dentry cache用于加速目录遍历和文件属性查找。这部分内存在slab中体现可以通过slabtop命令查看。3. 如何验证和评估操作系统缓存的效果理解了原理下一步就是动手验证。你不能只靠“感觉”需要有数据支撑。3.1 使用工具观察缓存命中率Linux 提供了强大的性能观测工具。vmstat看整体 I/O 和缓存效果$ vmstat 1 5 procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- r b swpd free buff cache si so bi bo in cs us sy id wa st 1 0 0 1234567 98765 4123456 0 0 0 5 10 20 10 5 85 0 0 0 0 0 1234000 98765 4123500 0 0 0 0 12 25 12 4 84 0 0cache: Page Cache 大小。bi(blocks in): 每秒从块设备读入的数据量KB。如果应用大量读数据且缓存未命中这个值会很高。bo(blocks out): 每秒写入块设备的数据量KB。wa(wait io): CPU 等待 I/O 的时间百分比。如果持续很高说明磁盘 I/O 是瓶颈。sar -B看页统计更精确的命中率$ sar -B 1 5 Linux ... (省略) 10:00:01 AM pgpgin/s pgpgout/s fault/s majflt/s pgfree/s pgscank/s pgscand/s pgsteal/s %vmeff 10:00:02 AM 0.00 5.00 100.20 0.00 200.50 0.00 0.00 0.00 0.00majflt/s(主要缺页中断)每秒发生的、需要从磁盘加载数据的缺页中断数。这个值越低越好高则说明缓存未命中多磁盘读频繁。%vmeff(页面回收效率)pgsteal/ (pgscankpgscand) * 100%越高说明页面回收效率高缓存利用好。但这不是直接的命中率指标。使用cachestat(来自 perf-tools 或 bcc) 这是更直观的工具但可能需要安装。# 假设已安装 bcc-tools $ sudo cachestat 1 HITS MISSES DIRTIES HITRATIO BUFFERS_MB CACHED_MB 10234 567 123 94.75% 15 4102 9987 612 145 94.23% 15 4105HITS/MISSES缓存命中/未命中次数。HITRATIO命中率。这是最直接的指标。对于以读为主的服务如静态文件服务器、数据库读库这个值应该非常高95%甚至99%。3.2 设计一个简单的测试案例不要一上来就在生产环境分析。可以自己构造场景测试顺序读# 1. 清空缓存观察初始状态 echo 3 | sudo tee /proc/sys/vm/drop_caches # 生产环境慎用仅用于测试。 vmstat 1 2 # 2. 第一次读取一个大文件如一个1GB的日志文件 time cat largefile.log /dev/null vmstat 1 2 # 观察 bi 很高cache 增长 # 3. 立即第二次读取同一个文件 time cat largefile.log /dev/null vmstat 1 2 # 观察 bi 几乎为0耗时极短第二次的time命令显示的时间会远小于第一次这就是 Page Cache 的威力。测试数据库查询 在一个有测试数据的数据库如 MySQL中反复执行一个需要扫描大量数据的SELECT语句。用vmstat或iostat观察磁盘读 (r/s,rkB/s) 的变化。第一次执行后数据页被加载到内存数据库的 Buffer Pool 和操作系统的 Page Cache。后续执行磁盘读会显著下降。3.3 评估标准什么时候缓存是有效的高命中率使用cachestat或观察majflt较低且应用性能良好。低磁盘利用率使用iostat -x 1查看%util在业务高峰时如果磁盘利用率很低比如20%而内存中cached很大说明缓存扛住了大部分压力。响应时间稳定应用的响应时间不会因为重复操作相同数据而出现巨大波动。关键经验操作系统缓存对“热数据”频繁访问的数据效果极佳。对于完全随机的、一次性的数据访问它无能为力。这就是 Redis 等应用缓存的价值所在——它们可以缓存经过复杂计算的结果、会话数据等非直接的磁盘数据块。4. 操作系统缓存 vs. Redis如何选择与配合现在回到标题我们不是要“迷信”Redis也不是要“抛弃”Redis而是要正确分层。4.1 职责与定位对比特性操作系统缓存 (Page Cache)Redis (应用缓存)管理方操作系统内核用户态应用程序缓存内容磁盘块文件内容、数据页数据结构字符串、哈希、列表等、对象、计算结果粒度固定大小页如4KB任意大小业务逻辑相关失效策略LRU最近最少使用为主受系统内存压力影响可配置TTL、LRU、LFU等共享性进程间自动共享通过文件系统需要网络访问所有客户端共享速度内存访问速度零网络开销内存访问速度 网络序列化/反序列化开销成本零额外成本利用空闲内存需要单独部署、维护占用额外内存和CPU4.2 实战中的协作策略一个典型的 Web 应用数据访问路径可能是这样的请求到达需要用户信息。第一层Redis 缓存。检查 Redis 中是否有以user:123为 key 的缓存对象。如果有直接返回路径结束。这避免了昂贵的数据库查询和业务逻辑计算。第二层数据库 Buffer Pool / 操作系统 Page Cache。如果 Redis 未命中则查询数据库。数据库先在自身的 Buffer Pool内存池中查找数据页。如果 Buffer Pool 未命中则向操作系统发起磁盘读请求。此时操作系统会检查 Page Cache。如果 Page Cache 命中数据直接从内存交给数据库避免了物理磁盘 I/O。第三层物理磁盘。如果 Page Cache 也未命中才发生真正的磁盘 I/O。所以Redis 缓存的是“计算结果”或“加工后的对象”而操作系统缓存的是“原始数据块”。它们不是替代关系而是互补关系。4.3 常见误区与避坑指南误区一内存“空闲”就是浪费。现象看到free命令显示内存只剩几百 MB就着急加内存或杀进程。判断先看available列和cached列。如果available内存充足且cached很大说明内存正在被高效用作缓存这是好事不是问题。盲目清理缓存如定时执行drop_caches会瞬间导致性能骤降。误区二用了 Redis磁盘 I/O 就应该为零。现象部署了 Redis 后发现磁盘读 (r/s) 依然存在。排查这很正常。Redis 缓存的是业务数据。数据库的日志写入redo log, binlog、全表扫描、备份任务、其他读写文件的进程都会产生磁盘 I/O。只要这些 I/O 不影响核心业务性能即可。需要关注的是核心业务 SQL 对应的磁盘 I/O 是否因 Redis 而减少。误区三缓存配置越大越好。操作系统缓存会占用内存。如果缓存过大挤占了应用程序如 JVM, MySQL的运行内存反而会导致 Swap 或 OOM。需要平衡。Redis同样过大的 Redis 缓存可能浪费内存资源。需要根据热点数据量合理设置 maxmemory 和淘汰策略。如何为数据库服务器规划内存 这是一个经典问题。假设一台服务器主要跑 MySQL。第一步为操作系统预留足够内存。通常 2-4GB 是安全的用于内核、进程和必要的 Page Cache。第二步为 MySQL 的innodb_buffer_pool_size分配内存。这是数据库最重要的缓存应尽可能大通常设为总内存的 50%-70%。第三步剩下的内存会自然地被操作系统用作 Page Cache来缓存数据文件、日志文件等。不要试图去“管理”这部分交给内核是最优的。5. 性能调优实战当缓存成为瓶颈时即使理解了缓存在实际运维中还是会遇到相关问题。以下是典型的排查思路。5.1 场景系统响应变慢waI/O等待高第一步定位 I/O 类型。使用iostat -x 1。$ iostat -x 1 Device r/s w/s rkB/s wkB/s await %util vda 0.00 50.00 0.00 20480.00 10.00 50.00看是读 (r/s) 高还是写 (w/s) 高以及哪个设备%util高。第二步关联进程。使用iotop需安装找到是哪个进程在疯狂 I/O。$ sudo iotop Total DISK READ: 0.00 B/s | Total DISK WRITE: 20.00 M/s Current DISK READ: 0.00 B/s | Current DISK WRITE: 20.00 M/s TID PRIO USER DISK READ DISK WRITE SWAPIN IO COMMAND 1234 be/4 mysql 0.00 B/s 20.00 M/s 0.00 % 10.00 % mysqld第三步分析原因。如果是读 I/O 高检查cachestat命中率。如果命中率低可能是内存不足缓存被挤占或者访问模式是完全随机、无法预测的。考虑能否优化查询加索引、避免全表扫描或者增加内存。如果是写 I/O 高可能是数据库大量更新、日志写入、或数据同步。检查 MySQL 的innodb_flush_log_at_trx_commit参数涉及一致性与性能的权衡或应用程序的写操作是否过于频繁。5.2 场景内存不足频繁使用 Swap使用free和vmstat确认观察si(swap in) 和so(swap out) 是否持续大于 0。使用ps或top排序找到内存消耗最大的进程。分析应用程序内存泄漏某个进程的 RES 内存持续增长不释放。需要结合jstat(Java)、gdb、valgrind等工具或代码排查。配置不当如 JVM 堆内存 (-Xmx) 或 MySQLbuffer_pool设置过大超过了物理内存导致操作系统被迫使用 Swap。需要调低应用内存配置为操作系统留出空间。真实内存不足业务增长现有内存确实无法承载。需要扩容。5.3 内核参数调优高级需谨慎大多数情况下内核默认参数已经优化得很好。不要轻易调整除非你明确知道自己在做什么。/proc/sys/vm/swappiness(值 0-100)控制系统使用 Swap 的倾向。值越高越倾向于使用 Swap。对于数据库服务器通常建议设为较低值如 1-10以尽量避免进程内存被换出因为数据库进程自己管理的内存如 Buffer Pool比 Page Cache 更重要。但也不能设为 0在某些极端内存压力下可能导致 OOM。# 临时修改 sudo sysctl vm.swappiness10 # 永久修改编辑 /etc/sysctl.conf vm.swappiness 10/proc/sys/vm/dirty_ratio与dirty_background_ratio控制“脏页”已修改但未写回磁盘的缓存页的比例。前者是绝对限制超过会阻塞应用程序直到写回完成后者是后台回写的触发点。对于写密集型且对数据丢失有一定容忍度的场景如日志收集可以适当调高以提升写入性能但会增加宕机时数据丢失的风险。最后的核心建议是优先优化应用程序和数据库其次是调整中间件如 Redis配置和使用策略最后再考虑操作系统内核参数的微调。操作系统缓存是默默奉献的“隐形王者”你的首要任务是理解它的存在和工作状态让它和 Redis 各司其职共同为你的系统性能保驾护航。下次做性能分析时别只看 Redis 的命中率也看一眼vmstat和iostat或许问题根源就在那里。