一文读懂Buffer与Cache:开启性能优化的大门

在计算机的世界里,内存管理就像是一场精密的交响乐指挥,协调着数据的流动与处理,对计算机系统的性能起着决定性作用。想象一下,计算机系统是一座繁忙的城市,内存就是城市中的交通枢纽,数据则是来来往往的车辆。如果没有良好的交通管理(内存管理),车辆(数据)就会拥堵,整个城市(计算机系统)的运行效率就会大幅下降。

而在内存管理这个大舞台上,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.0G1.2.3.

在这些信息中,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=201.2.3.
作用: dirty_ratio 参数定义了系统内存中脏页(已被修改但尚未写入磁盘)的最大比例。当脏页的比例达到或超过此值时,系统将启动同步写入操作,将脏页写入磁盘。影响: 控制脏页的及时写入,适当设置有助于避免频繁的磁盘写入操作。配置方式(参数的单位是百分比)

(2)dirty_background_ratio

复制
echo 10 > /proc/sys/vm/dirty_background_ratio 或 sysctl -w vm.dirty_background_ratio=101.2.3.
作用: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=1001.2.3.
作用: 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=101.2.3.
作用: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 01.2.3.

这里的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 01.2.3.4.5.6.

输出界面里, 内存部分的 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=5001.

然后再回到第一个终端,观察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 01.2.3.4.5.6.7.8.9.10.11.

通过观察 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=20481.2.3.4.

然后,再回到终端一,观察内存和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 01.2.3.4.5.6.7.8.9.

从这里你会看到,虽然同是写数据,写磁盘跟写文件的现象还是不同的。写磁盘时(也就是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/null1.2.3.4.

然后,再回到终端一,观察内存和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 01.2.3.4.5.6.

观察 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=10241.2.3.4.

然后,再回到终端一,观察内存和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 01.2.3.4.5.6.7.

观察 vmstat 的输出,你会发现读磁盘时(也就是bi大于0时),Buffer和Cache都在增长,但显然Buffer的增长快很多。这说明读磁盘时,数据缓存到了 Buffer 中。

当然,我想,经过上一个场景中两个案例的分析,你自己也可以对比得出这个结论:读文件时数据会缓存到 Cache 中,而读磁盘时数据会缓存到 Buffer 中。

到这里你应该发现了,虽然文档提供了对Buffer和Cache的说明,但是仍不能覆盖到所有的细节。比如说,今天我们了解到的这两点:

Buffer既可以用作“将要写入磁盘数据的缓存”,也可以用作“从磁盘读取数据的缓存”。Cache既可以用作“从文件读取数据的页缓存”,也可以用作“写文件的页缓存”。

这样,我们就回答了案例开始前的两个问题:简单来说,Buffer是对磁盘数据的缓存,而Cache是文件数据的缓存,它们既会用在读请求中,也会用在写请求中。

THE END
本站服务器由亿华云赞助提供-企业级高防云服务器