一文读懂Buffer与Cache:开启性能优化的大门
在计算机的世界里,内存管理就像是一场精密的交响乐指挥,协调着数据的流动与处理,对计算机系统的性能起着决定性作用。想象一下,计算机系统是一座繁忙的城市,内存就是城市中的交通枢纽,数据则是来来往往的车辆。如果没有良好的交通管理(内存管理),车辆(数据)就会拥堵,整个城市(计算机系统)的运行效率就会大幅下降。
而在内存管理这个大舞台上,Buffer(缓冲区)和 Cache(缓存)则是两位至关重要的角色,它们默默工作,却对系统性能有着深远影响。很多人对它们的概念感到困惑,常常将二者混为一谈,今天就让我们深入探究一下这两个神秘的存在,揭开它们的面纱,看看它们是如何在幕后助力计算机高效运行的 。
一、Buffer和Cache概述
从字面意思来看,Buffer 是缓冲区,Cache 是缓存 。它们都用于在内存中临时存储数据,但这两种 “临时存储” 又有着明显的区别。就好像你出门旅行,会带一个行李箱和一个随身小包。行李箱可以类比为 Buffer,它用来存放暂时不用,但后续可能会用到的物品,这些物品就像等待传输到其他设备的数据。而随身小包就像 Cache,里面装着你随时可能会用到的东西,比如手机、钱包,这些物品就像被频繁访问的数据,放在小包里能让你快速拿到,就像数据被缓存起来能被快速读取一样。
在 Linux 系统中,我们可以通过free命令来查看内存的使用情况 。打开终端,输入free -h(-h参数是为了让输出结果更易读),你会看到类似这样的信息:
在这些信息中,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
(2)dirty_background_ratio
2.2Cache:加速系统的秘密武器
Cache 的工作原理基于程序的局部性原理 ,即程序在一段时间内访问的数据往往集中在一个较小的区域 。它就像一个数据的 “快速通道”,将经常访问的数据复制到内存中速度更快的区域 。以数据库查询为例,当我们执行一个数据库查询语句时,查询结果会被存储在 Cache 中 。如果下次再执行相同或相似的查询语句,系统会首先检查 Cache 中是否已经存在该结果 。如果存在,就直接从 Cache 中读取数据返回给用户,而不需要再次执行复杂的数据库查询操作 。这大大减少了数据访问的时间,提高了系统的响应速度 。
Cache 通常采用一些替换算法,如最近最少使用(LRU)算法,来决定当 Cache 空间不足时,哪些数据需要被替换出去 。LRU 算法会将最近一段时间内最少被访问的数据替换掉,这样可以保证 Cache 中始终保存着最有可能被再次访问的数据 。除了数据库查询,Cache 在 CPU 与内存之间也起着重要作用 。
由于 CPU 的运行速度远远快于内存,为了减少 CPU 等待内存数据的时间,在 CPU 和内存之间设置了 Cache 。CPU 首先会在 Cache 中查找需要的数据,如果找到(命中),就可以快速获取数据进行处理;如果没找到(未命中),才会从内存中读取数据,并将读取的数据同时存入 Cache 中,以便下次访问时能够更快获取 。
相关系统参数(1)vfs_cache_pressure
例如:swappiness
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表示每秒输出一次数据),可以看到如下信息:
这里的buff和cache分别对应 Buffer 和 Cache 使用的内存大小,单位是 KB ,bi和bo分别表示块设备读取和写入的大小,单位为块 / 秒 。由于 Linux 中块的大小是 1KB,所以这个单位也就等价于 KB/s 。
3.1磁盘和文件写案例
我们先来模拟第一个场景;首先,在第一个终端,运行下面这个vmstat 命令:
输出界面里, 内存部分的 buff 和 cache ,以及 io 部分的 bi 和 bo 就是我们要关注的重点。
buff 和 cache 就是我们前面看到的 Buffers 和 Cache,单位是 KB。bi 和 bo 则分别表示块设备读取和写入的大小,单位为块/秒。因为 Linux 中块的大小是 1KB,所以这个单位也就等价于 KB/s。正常情况下,空闲系统中,你应该看到的是,这几个值在多次结果中一直保持不变。
接下来,到第二个终端执行 dd 命令,通过读取随机设备,生成一个500MB大小的文件:
然后再回到第一个终端,观察Buffer和Cache的变化情况:
通过观察 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的随机数据:
然后,再回到终端一,观察内存和I/O的变化情况:
从这里你会看到,虽然同是写数据,写磁盘跟写文件的现象还是不同的。写磁盘时(也就是bo大于 0 时),Buffer和Cache都在增长,但显然Buffer的增长快得多。
这说明,写磁盘用到了大量的Buffer,这跟我们在文档中查到的定义是一样的。
对比两个案例,我们发现,写文件时会用到 Cache 缓存数据,而写磁盘则会用到 Buffer 来缓存数据。所以,回到刚刚的问题,虽然文档上只提到,Cache是文件读的缓存,但实际上,Cache也会缓存写文件时的数据。
3.2磁盘和文件读案例
了解了磁盘和文件写的情况,我们再反过来想,磁盘和文件读的时候,又是怎样的呢?
我们回到第二个终端,运行下面的命令;清理缓存后,从文件/tmp/file中,读取数据写入空设备:
然后,再回到终端一,观察内存和I/O的变化情况:
观察 vmstat 的输出,你会发现读取文件时(也就是bi大于0时),Buffer保持不变,而Cache则在不停增长。这跟我们查到的定义“Cache是对文件读的页缓存”是一致的。
那么,磁盘读又是什么情况呢?
我们再运行第二个案例来看看:首先,回到第二个终端,运行下面的命令。
清理缓存后,从磁盘分区 /dev/sda1中读取数据,写入空设备:
然后,再回到终端一,观察内存和I/O的变化情况:
观察 vmstat 的输出,你会发现读磁盘时(也就是bi大于0时),Buffer和Cache都在增长,但显然Buffer的增长快很多。这说明读磁盘时,数据缓存到了 Buffer 中。
当然,我想,经过上一个场景中两个案例的分析,你自己也可以对比得出这个结论:读文件时数据会缓存到 Cache 中,而读磁盘时数据会缓存到 Buffer 中。
到这里你应该发现了,虽然文档提供了对Buffer和Cache的说明,但是仍不能覆盖到所有的细节。比如说,今天我们了解到的这两点:
Buffer既可以用作“将要写入磁盘数据的缓存”,也可以用作“从磁盘读取数据的缓存”。Cache既可以用作“从文件读取数据的页缓存”,也可以用作“写文件的页缓存”。这样,我们就回答了案例开始前的两个问题:简单来说,Buffer是对磁盘数据的缓存,而Cache是文件数据的缓存,它们既会用在读请求中,也会用在写请求中。