【linux性能优化】理解内存的Buffer和Cache

内存和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数据的来源

碰到看不明白的指标时该怎么办呢?

不懂就去查手册,用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中的CachedSReclaimable之和

man free的说明得知这些数值都来自/proc/meminfo,但更具体的BuffersCachedSReclaimable的含义,还是没有说清楚

有没有更简单、更准确的方法,来获得它们的含义呢?

二、proc文件系统

2.1 /proc作用

在CPU性能模块中曾提到过,/proc是Linux内核提供的一种特殊文件系统,是用户跟内核交互的接口,比如用户可以从/proc中:

  • 查询内核的运行状态和配置选项
  • 查询进程的运行状态
  • 统计数据

当然,也可以通过/proc来修改内核的配置

proc文件系统同时也是很多性能工具的最终数据来源,比如free就是通过读取/proc/meminfo得到内存的使用情况

2.2 man proc

继续说回/proc/meminfo,既然Buffers、Cached、SReclaimable这几个指标不容易理解,那么继续查proc文件系统,获取它们的详细定义

执行man proc可以得到proc文件系统的详细文档

Buffers %lu
    Relatively temporary storage for raw disk blocks that shouldn't get tremendously large (20MB or s
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.

注意: 这个文档比较长,最好搜索一下(比如搜索 meminfo),以便更快定位到内存部分

2.3 指标详细定义

通过这个文档可以看到:

  • Buffers

对原始磁盘块的临时存储,也就是用来缓存磁盘的数据,通常不会特别大(20MB左右)
这样,内核就可以把分散的写集中起来,统一优化磁盘的写入,比如可以把多次小的写合并成单次大的写等等

  • Cached

从磁盘读取文件的页缓存,也就是用来缓存从文件读取的数据
这样,下次访问这些文件数据时,就可以直接从内存中快速获取,而不需要再次访问缓慢的磁盘

  • SReclaimable和Slab

SReclaimable是Slab的一部分
Slab包括两部分,其中:
可回收部分,用SReclaimable记录
不可回收部分,用SUnreclaim记录

2.4 问题思考

好了,终于找到了这三个指标的详细定义,总算弄明白Buffer和Cache了,不过知道这个定义就真的理解了吗?想想能不能回答出来这两个问题:

  1. Buffer的文档没有提到这是磁盘读数据还是写数据的缓存,那么它会不会也缓存从磁盘中读取的数据呢?
  2. Cache是对从文件读取数据的缓存,那么它是不是也会缓存写文件的数据呢?

为了解答这两个问题,接下来将用几个案例来展示, Buffer和Cache在不同场景下的使用情况

三、案例

打开两个终端,为了减少缓存的影响,在第一个终端中运行下面的命令来清理系统缓存:

# 清理文件页、目录项、Inodes等各种缓存
$ echo 3 > /proc/sys/vm/drop_caches

这里的/proc/sys/vm/drop_caches,就是通过proc文件系统修改内核行为的一个示例,写入3表示清理文件页、目录项、Inodes等各种缓存

3.1 场景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

然后再回到第一个终端,观察BufferCache的变化情况:

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只出现了一次488KB/s,bo只有一次4KB

  • 过一段时间后

出现大量的块设备写,比如bo变成了122880

  • dd命令结束后

Cache不再增长,但块设备写还会持续一段时间,并且多次I/O写的结果加起来,才是dd要写的 500M的数据

把这个结果跟刚刚了解到的Cache的定义做个对比则有点晕乎,为什么文档上说Cache是文件读的页缓存,怎么现在写文件也有它的份?

这个疑问暂且先记下来,接着再来看另一个磁盘写的案例。等这两个案例结束后,再统一进行分析

不过,对于接下来的案例必须强调一点:

下面的命令对环境要求很高,需要系统配置多块磁盘,并且磁盘分区/dev/sdb1还要处于未使用状态。如果只有一块磁盘,千万不要尝试,否则将会对磁盘分区造成损坏

如果系统符合标准,则可以继续在第二个终端中运行下面的命令:

# 首先清理缓存
$ echo 3 > /proc/sys/vm/drop_caches
# 然后运行dd命令向磁盘分区/dev/sdb1写入2G数据
$ dd if=/dev/urandom of=/dev/sdb1 bs=1M count=2048

清理缓存后,向磁盘分区/dev/sdb1写入2GB的随机数据

然后,再回到终端一观察内存和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 场景2:磁盘和文件读案例

了解了磁盘和文件写的情况,再反过来想磁盘和文件读的时候,又是怎样的呢?

回到第二个终端运行下面的命令:

# 首先清理缓存
$ echo 3 > /proc/sys/vm/drop_caches
# 运行dd命令读取文件数据
$ dd if=/tmp/file of=/dev/null

清理缓存后,从文件/tmp/file中读取数据写入空设备

然后,再回到终端一观察内存和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是对文件读的页缓存是一致的

那么磁盘读又是什么情况呢?再运行第二个案例来看看

首先,回到第二个终端运行下面的命令:

# 首先清理缓存
$ echo 3 > /proc/sys/vm/drop_caches
# 运行dd命令读取文件
$ dd if=/dev/sda1 of=/dev/null bs=1M count=1024

清理缓存后,从磁盘分区/dev/sda1中读取数据,写入空设备

然后,再回到终端一观察内存和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文件系统也是好帮手,它呈现了系统内部的运行状态,同时也是很多性能工具的数据来源,是辅助排查性能问题的好方法

上一篇:作为高级Java,你应该了解的Linux知识


下一篇:linux通过meminfo 与 slab 定位内存泄漏