You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

318 lines
18 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 16 | 基础篇怎么理解内存中的Buffer和Cache
你好,我是倪朋飞。
上一节,我们梳理了 Linux 内存管理的基本原理,并学会了用 free 和 top 等工具,来查看系统和进程的内存使用情况。
内存和 CPU 的关系非常紧密,而内存管理本身也是很复杂的机制,所以感觉知识很硬核、很难啃,都是正常的。但还是那句话,初学时不用非得理解所有内容,继续往后学,多理解相关的概念并配合一定的实践之后,再回头复习往往会容易不少。当然,基本功不容放弃。
在今天的内容开始之前,我们先来回顾一下系统的内存使用情况,比如下面这个 free 输出界面:
```
# 注意不同版本的free输出可能会有所不同
$ free
total used free shared buff/cache available
Mem: 8169348 263524 6875352 668 1030472 7611064
Swap: 0 0 0
```
显然这个界面包含了物理内存Mem和交换分区Swap的具体使用情况比如总内存、已用内存、缓存、可用内存等。其中缓存是 Buffer和Cache两部分的总和 。
这里的大部分指标都比较容易理解,但 Buffer和 Cache可能不太好区分。从字面上来说Buffer是缓冲区而Cache是缓存两者都是数据在内存中的临时存储。那么你知道这两种“临时存储”有什么区别吗
今天内容接下来的部分Buffer和Cache我会都用英文来表示避免跟文中的“缓存”一词混淆。而文中的“缓存”则通指内存中的临时存储。
## free数据的来源
在我正式讲解两个概念前你可以先想想你有没有什么途径来进一步了解它们除了中文翻译直接得到概念别忘了Buffer和Cache还是我们用free获得的指标。
还记得我之前讲过的,碰到看不明白的指标时该怎么办吗?
估计你想起来了,不懂就去查手册。用 man 命令查询 free 的文档,就可以找到对应指标的详细说明。比如,我们执行 man free ,就可以看到下面这个界面。
```
buffers
Memory used by kernel buffers (Buffers in /proc/meminfo)
cache Memory used by the page cache and slabs (Cached and SReclaimable in /proc/meminfo)
buff/cache
Sum of buffers and cache
```
从free的手册中你可以看到 buffer 和 cache 的说明。
* Buffers 是内核缓冲区用到的内存,对应的是 `/proc/meminfo` 中的 Buffers 值。
* Cache 是内核页缓存和Slab用到的内存对应的是 `/proc/meminfo` 中的 Cached 与 SReclaimable 之和。
这里的说明告诉我们,这些数值都来自 `/proc/meminfo`,但更具体的 Buffers、Cached和SReclaimable 的含义,还是没有说清楚。
要弄明白它们到底是什么,我估计你第一反应就是去百度或者 Google一下。虽然大部分情况下网络搜索能给出一个答案。但是且不说筛选信息花费的时间精力对你来说这个答案的准确性也是很难保证的。
要注意,网上的结论可能是对的,但是很可能跟你的环境并不匹配。最简单来说,同一个指标的具体含义,就可能因为内核版本、性能工具版本的不同而有挺大差别。这也是为什么,我总在专栏中强调通用思路和方法,而不是让你死记结论。对于案例实践来说,机器环境就是我们的最大限制。
那么,有没有更简单、更准确的方法,来查询它们的含义呢?
## proc文件系统
我在前面 CPU 性能模块就曾经提到过,/proc 是 Linux 内核提供的一种特殊文件系统,是用户跟内核交互的接口。比方说,用户可以从 /proc 中查询内核的运行状态和配置选项,查询进程的运行状态、统计数据等,当然,你也可以通过 /proc 来修改内核的配置。
proc 文件系统同时也是很多性能工具的最终数据来源。比如我们刚才看到的 free ,就是通过读取`/proc/meminfo`,得到内存的使用情况。
继续说回`/proc/meminfo`,既然 Buffers、Cached、SReclaimable 这几个指标不容易理解,那我们还得继续查 proc 文件系统,获取它们的详细定义。
执行 `man proc`,你就可以得到 proc 文件系统的详细文档。
注意这个文档比较长,你最好搜索一下(比如搜索 meminfo以便更快定位到内存部分。
```
Buffers %lu
Relatively temporary storage for raw disk blocks that shouldn't get tremendously large (20MB or so).
Cached %lu
In-memory cache for files read from the disk (the page cache). Doesn't include SwapCached.
...
SReclaimable %lu (since Linux 2.6.19)
Part of Slab, that might be reclaimed, such as caches.
SUnreclaim %lu (since Linux 2.6.19)
Part of Slab, that cannot be reclaimed on memory pressure.
```
通过这个文档,我们可以看到:
* Buffers 是对原始磁盘块的临时存储,也就是用来**缓存磁盘的数据**通常不会特别大20MB左右。这样内核就可以把分散的写集中起来统一优化磁盘的写入比如可以把多次小的写合并成单次大的写等等。
* Cached 是从磁盘读取文件的页缓存,也就是用来**缓存从文件读取的数据**。这样,下次访问这些文件数据时,就可以直接从内存中快速获取,而不需要再次访问缓慢的磁盘。
* SReclaimable 是 Slab 的一部分。Slab包括两部分其中的可回收部分用 SReclaimable 记录;而不可回收部分,用 SUnreclaim 记录。
好了我们终于找到了这三个指标的详细定义。到这里你是不是长舒一口气满意地想着总算弄明白Buffer 和 Cache了。不过知道这个定义就真的理解了吗这里我给你提了两个问题你先想想能不能回答出来。
第一个问题Buffer 的文档没有提到这是磁盘读数据还是写数据的缓存,而在很多网络搜索的结果中都会提到 Buffer 只是对**将要写入磁盘数据**的缓存。那反过来说,它会不会也缓存从磁盘中读取的数据呢?
第二个问题文档中提到Cache 是对从文件读取数据的缓存,那么它是不是也会缓存写文件的数据呢?
为了解答这两个问题,接下来,我将用几个案例来展示, Buffer 和 Cache 在不同场景下的使用情况。
## 案例
### 你的准备
跟前面实验一样,今天的案例也是基于 Ubuntu 18.04,当然,其他 Linux 系统也适用。我的案例环境是这样的。
* 机器配置2 CPU8GB 内存。
* 预先安装 sysstat 包,如 apt install sysstat。
之所以要安装 sysstat ,是因为我们要用到 vmstat ,来观察 Buffer 和 Cache 的变化情况。虽然从 /proc/meminfo 里也可以读到相同的结果,但毕竟还是 vmstat 的结果更加直观。
另外,这几个案例使用了 dd 来模拟磁盘和文件的 I/O所以我们也需要观测 I/O 的变化情况。
上面的工具安装完成后,你可以打开两个终端,连接到 Ubuntu 机器上。
准备环节的最后一步,为了减少缓存的影响,记得在第一个终端中,运行下面的命令来清理系统缓存:
```
# 清理文件页、目录项、Inodes等各种缓存
$ echo 3 > /proc/sys/vm/drop_caches
```
这里的 `/proc/sys/vm/drop_caches` ,就是通过 proc 文件系统修改内核行为的一个示例,写入 3 表示清理文件页、目录项、Inodes等各种缓存。这几种缓存的区别你暂时不用管后面我们都会讲到。
### 场景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/sbo 则只有一次 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也会缓存写文件时的数据。
### 场景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是文件数据的缓存它们既会用在读请求中也会用在写请求中**。
## 小结
今天,我们一起探索了内存性能中 Buffer 和 Cache 的详细含义。Buffer和Cache分别缓存磁盘和文件系统的读写数据。
* 从写的角度来说,不仅可以优化磁盘和文件的写入,对应用程序也有好处,应用程序可以在数据真正落盘前,就返回去做其他工作。
* 从读的角度来说既可以加速读取那些需要频繁访问的数据也降低了频繁I/O对磁盘的压力。
除了探索的内容本身,这个探索过程对你应该也有所启发。在排查性能问题时,由于各种资源的性能指标太多,我们不可能记住所有指标的详细含义。那么,准确高效的手段——查文档,就非常重要了。
你一定要养成查文档的习惯并学会解读这些性能指标的详细含义。此外proc 文件系统也是我们的好帮手。它为我们呈现了系统内部的运行状态,同时也是很多性能工具的数据来源,是辅助排查性能问题的好方法。
## 思考
最后,我想给你留一个思考题。
我们已经知道,可以使用 ps、top 或者 proc 文件系统,来获取进程的内存使用情况。那么,如何统计出所有进程的物理内存使用量呢?
提示:要避免重复计算多个进程同时占用的内存,像是页缓存、共享内存这类。如果你把 ps、top 得到的数据直接相加,就会出现重复计算的问题。
这里,我推荐从 `/proc/<pid>/smaps` 入手。前面内容里,我并没有直接讲过`/proc/<pid>/smaps`文件中各个指标含义,所以,需要你自己动手查 proc 文件系统的文档,解读并回答这个问题。
欢迎在留言区和我讨论,也欢迎你把这篇文章分享给你的同事、朋友。我们一起在实战中演练,在交流中进步。