一文读懂Buffer与Cache:开启性能优化的大门
原创 往事敬秋风 深度Linux 2025年05月23日 07:31 湖南
在计算机的世界里,内存管理就像是一场精密的交响乐指挥,协调着数据的流动与处理,对计算机系统的性能起着决定性作用。想象一下,计算机系统是一座繁忙的城市,内存就是城市中的交通枢纽,数据则是来来往往的车辆。如果没有良好的交通管理(内存管理),车辆(数据)就会拥堵,整个城市(计算机系统)的运行效率就会大幅下降。
而在内存管理这个大舞台上,Buffer(缓冲区)和 Cache(缓存)则是两位至关重要的角色,它们默默工作,却对系统性能有着深远影响。很多人对它们的概念感到困惑,常常将二者混为一谈,今天就让我们深入探究一下这两个神秘的存在,揭开它们的面纱,看看它们是如何在幕后助力计算机高效运行的 。
一、Buffer和Cache概述
从字面意思来看,Buffer 是缓冲区,Cache 是缓存 。它们都用于在内存中临时存储数据,但这两种 “临时存储” 又有着明显的区别。就好像你出门旅行,会带一个行李箱和一个随身小包。行李箱可以类比为 Buffer,它用来存放暂时不用,但后续可能会用到的物品,这些物品就像等待传输到其他设备的数据。而随身小包就像 Cache,里面装着你随时可能会用到的东西,比如手机、钱包,这些物品就像被频繁访问的数据,放在小包里能让你快速拿到,就像数据被缓存起来能被快速读取一样。
在 Linux 系统中,我们可以通过free命令来查看内存的使用情况 。打开终端,输入free -h(-h参数是为了让输出结果更易读),你会看到类似这样的信息:
total used free shared buff/cache available
Mem: 7.7G 2.0G 3.7G 113M 2.0G 5.3G
Swap: 2.0G 0B 2.0G
在这些信息中,buff/cache这一列引起了我们的注意,它就是 Buffer 和 Cache 的内存使用总和 。而实际上,free命令的统计数据是来自于/proc/meminfo这个文件 。用cat /proc/meminfo命令查看,会看到更详细的内存信息,其中Buffers和Cached这两个字段,分别对应着 Buffer 和 Cache 使用的内存大小 。
那么,Buffers、Cached和SReclaimable具体是什么含义呢?Buffers是内核缓冲区用到的内存,它主要用于缓存磁盘的数据 。比如,当我们向磁盘写入数据时,数据不会立刻被写入磁盘,而是先存储在Buffers中,等积累到一定量或者满足特定条件时,再统一写入磁盘,这样可以减少磁盘 I/O 的次数,提高写入效率 。Cached是内核页缓存和 Slab 用到的内存,主要用于缓存从文件读取的数据 。
当我们读取一个文件时,数据会被缓存到Cached中,如果下次再读取相同的文件内容,就可以直接从内存中获取,大大加快了读取速度 。SReclaimable是 Slab 的一部分,Slab 是内核中用于管理内存的一种机制,SReclaimable表示 Slab 中可回收的部分 。
二、Buffer与Cache工作原理
2.1Buffer的工作原理
Buffer 就像是数据传输过程中的一个临时停靠站 。当计算机与不同速度的设备进行数据交换时,比如内存与硬盘之间,由于硬盘的读写速度相对内存来说非常慢,如果没有 Buffer,内存就需要一直等待硬盘完成数据传输,这会极大地浪费内存的性能 。而有了 Buffer,当内存要向硬盘写入数据时,数据会先被存储到 Buffer 中 。假设我们要将一个大文件写入硬盘,文件数据会按一定大小的块依次存入 Buffer 。
当 Buffer 中的数据达到一定量(比如一个磁盘块的大小),或者满足特定的写入条件(如操作系统的写入调度策略)时,这些数据就会被一次性写入硬盘 。这样做的好处是,减少了硬盘的读写次数 。因为如果每次有少量数据就直接写入硬盘,硬盘的磁头需要频繁移动来定位数据位置,这会花费大量时间 。而通过 Buffer 的缓冲,将分散的小写入操作合并成大的写入操作,大大提高了数据传输的效率 。
同时,在数据读取时,Buffer 也起着类似的作用 。当从硬盘读取数据时,硬盘会将数据先读取到 Buffer 中,内存再从 Buffer 中读取数据,这就避免了内存直接与速度较慢的硬盘频繁交互,保证了数据传输的稳定性和高效性 。
当应用程序请求从磁盘读取数据时,内核会先检查Buffer中是否已经存在相应的数据块。如果存在,内核会直接从Buffer返回数据,避免了对物理磁盘的读取。如果数据不在Buffer中,内核会将数据块从磁盘读取到Buffer中,并返回给应用程序。这样,Buffer在一定程度上减少了对磁盘的访问次数,提高了I/O性能。
相关系统参数
(1)dirty_ratio
echo 20 > /proc/sys/vm/dirty_ratio
或
sysctl -w vm.dirty_ratio=20
-
作用: dirty_ratio 参数定义了系统内存中脏页(已被修改但尚未写入磁盘)的最大比例。当脏页的比例达到或超过此值时,系统将启动同步写入操作,将脏页写入磁盘。
-
影响: 控制脏页的及时写入,适当设置有助于避免频繁的磁盘写入操作。
-
配置方式(参数的单位是百分比)
(2)dirty_background_ratio
echo 10 > /proc/sys/vm/dirty_background_ratio
或
sysctl -w vm.dirty_background_ratio=10
-
作用:dirty_background_ratio 参数定义了当脏页的比例超过此值时,系统会触发后台写入操作。后台写入是指将脏页异步地写入磁盘,不会引起进程阻塞。
-
影响: 控制后台写入的启动条件,避免系统过早地触发写入操作,从而提高系统性能。
-
配置方式, 可通过修改 /proc/sys/vm/dirty_background_ratio 文件或使用 sysctl 命令进行配置。(参数的单位是百分比)
2.2Cache:加速系统的秘密武器
Cache 的工作原理基于程序的局部性原理 ,即程序在一段时间内访问的数据往往集中在一个较小的区域 。它就像一个数据的 “快速通道”,将经常访问的数据复制到内存中速度更快的区域 。以数据库查询为例,当我们执行一个数据库查询语句时,查询结果会被存储在 Cache 中 。如果下次再执行相同或相似的查询语句,系统会首先检查 Cache 中是否已经存在该结果 。如果存在,就直接从 Cache 中读取数据返回给用户,而不需要再次执行复杂的数据库查询操作 。这大大减少了数据访问的时间,提高了系统的响应速度 。
Cache 通常采用一些替换算法,如最近最少使用(LRU)算法,来决定当 Cache 空间不足时,哪些数据需要被替换出去 。LRU 算法会将最近一段时间内最少被访问的数据替换掉,这样可以保证 Cache 中始终保存着最有可能被再次访问的数据 。除了数据库查询,Cache 在 CPU 与内存之间也起着重要作用 。
由于 CPU 的运行速度远远快于内存,为了减少 CPU 等待内存数据的时间,在 CPU 和内存之间设置了 Cache 。CPU 首先会在 Cache 中查找需要的数据,如果找到(命中),就可以快速获取数据进行处理;如果没找到(未命中),才会从内存中读取数据,并将读取的数据同时存入 Cache 中,以便下次访问时能够更快获取 。
相关系统参数
(1)vfs_cache_pressure
echo 100 > /proc/sys/vm/vfs_cache_pressure
或
sysctl -w vm.vfs_cache_pressure=100
-
作用: vfs_cache_pressure 参数用于调整内核对 dentry 和 inode 缓存的倾向性。较大的值使内核倾向于回收 dentry,而较小的值使内核倾向于回收 inode。
-
影响: 控制文件系统缓存的回收策略,影响文件系统性能。较大的值有助于加速缓存的回收,从而释放内存。
-
配置方式: 可通过修改 /proc/sys/vm/vfs_cache_pressure 文件或使用 sysctl 命令进行配置。
例如:swappiness
echo 10 > /proc/sys/vm/swappiness
或
sysctl -w vm.swappiness=10
-
作用:swappiness 参数用于调整内核在内存不足时将数据移动到交换空间的倾向性。值的范围是 0 到 100,0 表示尽量不使用交换空间,100 表示尽量使用交换空间。
-
影响: 控制系统对交换空间的利用,较小的值有助于减少对交换空间的使用,提高整体性能。
-
配置方式: 可通过修改 /proc/sys/vm/swappiness 文件或使用 sysctl 命令进行配置。
3.3Buffer和Cache的区别
(1)存储内容
-
Buffer存储的是I/O操作的数据块,通常是对物理设备的读写请求的中介。
-
Cache存储的是文件系统的数据块,包括文件的元数据和实际内容。
(2)读取方式
-
Buffer主要用于减少对物理设备的读写次数,通过缓存I/O操作提高性能。
-
Cache更侧重于文件系统的读取,通过缓存文件数据和元数据提高文件系统的整体读取速度。
(3)清理策略
-
Buffer中的数据通常被操作系统维护,不容易手动清理。
-
Cache的内容可以通过手动或自动的方式进行清理,以释放内存空间。
三、实战案例分析
为了更直观地理解 Buffer 和 Cache 的工作方式,我们通过几个实际案例来进行分析 。在 Linux 系统中,我们可以使用vmstat命令来实时监控内存和 I/O 的使用情况 。打开终端,输入vmstat 1(1表示每秒输出一次数据),可以看到如下信息:
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 999960 2464 135840 0 0 1 2 5 29 0 0 100 0 0
这里的buff和cache分别对应 Buffer 和 Cache 使用的内存大小,单位是 KB ,bi和bo分别表示块设备读取和写入的大小,单位为块 / 秒 。由于 Linux 中块的大小是 1KB,所以这个单位也就等价于 KB/s 。
3.1磁盘和文件写案例
我们先来模拟第一个场景;首先,在第一个终端,运行下面这个vmstat 命令:
# 每隔1秒输出1组数据
$ vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
0 0 0 7743608 1112 92168 0 0 0 0 52 152 0 1 100 0 0
0 0 0 7743608 1112 92168 0 0 0 0 36 92 0 0 100 0 0
输出界面里, 内存部分的 buff 和 cache ,以及 io 部分的 bi 和 bo 就是我们要关注的重点。
-
buff 和 cache 就是我们前面看到的 Buffers 和 Cache,单位是 KB。
-
bi 和 bo 则分别表示块设备读取和写入的大小,单位为块/秒。因为 Linux 中块的大小是 1KB,所以这个单位也就等价于 KB/s。
正常情况下,空闲系统中,你应该看到的是,这几个值在多次结果中一直保持不变。
接下来,到第二个终端执行 dd 命令,通过读取随机设备,生成一个500MB大小的文件:
$ dd if=/dev/urandom of=/tmp/file bs=1M count=500
然后再回到第一个终端,观察Buffer和Cache的变化情况:
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
0 0 0 7499460 1344 230484 0 0 0 0 29 145 0 0 100 0 0
1 0 0 7338088 1752 390512 0 0 488 0 39 558 0 47 53 0 0
1 0 0 7158872 1752 568800 0 0 0 4 30 376 1 50 49 0 0
1 0 0 6980308 1752 747860 0 0 0 0 24 360 0 50 50 0 0
0 0 0 6977448 1752 752072 0 0 0 0 29 138 0 0 100 0 0
0 0 0 6977440 1760 752080 0 0 0 152 42 212 0 1 99 1 0
...
0 1 0 6977216 1768 752104 0 0 4 122880 33 234 0 1 51 49 0
0 1 0 6977440 1768 752108 0 0 0 10240 38 196 0 0 50 50 0
通过观察 vmstat 的输出,我们发现,在dd命令运行时, Cache在不停地增长,而Buffer基本保持不变。
再进一步观察I/O的情况,你会看到,在 Cache 刚开始增长时,块设备 I/O 很少,bi 只出现了一次 488 KB/s,bo 则只有一次 4KB。而过一段时间后,才会出现大量的块设备写,比如 bo 变成了122880。
当 dd 命令结束后,Cache 不再增长,但块设备写还会持续一段时间,并且,多次 I/O 写的结果加起来,才是 dd 要写的 500M 的数据。
把这个结果,跟我们刚刚了解到的Cache的定义做个对比,你可能会有点晕乎。为什么前面文档上说 Cache 是文件读的页缓存,怎么现在写文件也有它的份?
这个疑问,我们暂且先记下来,接着再来看另一个磁盘写的案例。两个案例结束后,我们再统一进行分析。
不过,对于接下来的案例,我必须强调一点:下面的命令对环境要求很高,需要你的系统配置多块磁盘,并且磁盘分区 /dev/sdb1 还要处于未使用状态。如果你只有一块磁盘,千万不要尝试,否则将会对你的磁盘分区造成损坏。
如果你的系统符合标准,就可以继续在第二个终端中,运行下面的命令。清理缓存后,向磁盘分区/dev/sdb1 写入2GB的随机数据:
# 首先清理缓存
$ echo 3 > /proc/sys/vm/drop_caches
# 然后运行dd命令向磁盘分区/dev/sdb1写入2G数据
$ dd if=/dev/urandom of=/dev/sdb1 bs=1M count=2048
然后,再回到终端一,观察内存和I/O的变化情况:
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 7584780 153592 97436 0 0 684 0 31 423 1 48 50 2 0
1 0 0 7418580 315384 101668 0 0 0 0 32 144 0 50 50 0 0
1 0 0 7253664 475844 106208 0 0 0 0 20 137 0 50 50 0 0
1 0 0 7093352 631800 110520 0 0 0 0 23 223 0 50 50 0 0
1 1 0 6930056 790520 114980 0 0 0 12804 23 168 0 50 42 9 0
1 0 0 6757204 949240 119396 0 0 0 183804 24 191 0 53 26 21 0
1 1 0 6591516 1107960 123840 0 0 0 77316 22 232 0 52 16 33 0
从这里你会看到,虽然同是写数据,写磁盘跟写文件的现象还是不同的。写磁盘时(也就是bo大于 0 时),Buffer和Cache都在增长,但显然Buffer的增长快得多。
这说明,写磁盘用到了大量的Buffer,这跟我们在文档中查到的定义是一样的。
对比两个案例,我们发现,写文件时会用到 Cache 缓存数据,而写磁盘则会用到 Buffer 来缓存数据。所以,回到刚刚的问题,虽然文档上只提到,Cache是文件读的缓存,但实际上,Cache也会缓存写文件时的数据。
3.2磁盘和文件读案例
了解了磁盘和文件写的情况,我们再反过来想,磁盘和文件读的时候,又是怎样的呢?
我们回到第二个终端,运行下面的命令;清理缓存后,从文件/tmp/file中,读取数据写入空设备:
# 首先清理缓存
$ echo 3 > /proc/sys/vm/drop_caches
# 运行dd命令读取文件数据
$ dd if=/tmp/file of=/dev/null
然后,再回到终端一,观察内存和I/O的变化情况:
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
0 1 0 7724164 2380 110844 0 0 16576 0 62 360 2 2 76 21 0
0 1 0 7691544 2380 143472 0 0 32640 0 46 439 1 3 50 46 0
0 1 0 7658736 2380 176204 0 0 32640 0 54 407 1 4 50 46 0
0 1 0 7626052 2380 208908 0 0 32640 40 44 422 2 2 50 46 0
观察 vmstat 的输出,你会发现读取文件时(也就是bi大于0时),Buffer保持不变,而Cache则在不停增长。这跟我们查到的定义“Cache是对文件读的页缓存”是一致的。
那么,磁盘读又是什么情况呢?
我们再运行第二个案例来看看:首先,回到第二个终端,运行下面的命令。
清理缓存后,从磁盘分区 /dev/sda1中读取数据,写入空设备:
# 首先清理缓存
$ echo 3 > /proc/sys/vm/drop_caches
# 运行dd命令读取文件
$ dd if=/dev/sda1 of=/dev/null bs=1M count=1024
然后,再回到终端一,观察内存和I/O的变化情况:
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
0 0 0 7225880 2716 608184 0 0 0 0 48 159 0 0 100 0 0
0 1 0 7199420 28644 608228 0 0 25928 0 60 252 0 1 65 35 0
0 1 0 7167092 60900 608312 0 0 32256 0 54 269 0 1 50 49 0
0 1 0 7134416 93572 608376 0 0 32672 0 53 253 0 0 51 49 0
0 1 0 7101484 126320 608480 0 0 32748 0 80 414 0 1 50 49 0
观察 vmstat 的输出,你会发现读磁盘时(也就是bi大于0时),Buffer和Cache都在增长,但显然Buffer的增长快很多。这说明读磁盘时,数据缓存到了 Buffer 中。
当然,我想,经过上一个场景中两个案例的分析,你自己也可以对比得出这个结论:读文件时数据会缓存到 Cache 中,而读磁盘时数据会缓存到 Buffer 中。
到这里你应该发现了,虽然文档提供了对Buffer和Cache的说明,但是仍不能覆盖到所有的细节。比如说,今天我们了解到的这两点:
-
Buffer既可以用作“将要写入磁盘数据的缓存”,也可以用作“从磁盘读取数据的缓存”。
-
Cache既可以用作“从文件读取数据的页缓存”,也可以用作“写文件的页缓存”。
这样,我们就回答了案例开始前的两个问题:简单来说,Buffer是对磁盘数据的缓存,而Cache是文件数据的缓存,它们既会用在读请求中,也会用在写请求中。