Linux 里Buffer和Cache的定义及使用

Buffer 和 Cache 的介绍

查看内存使用情况

# 注意不同版本的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 是文件数据的缓存,它们既会用在读请求中,也会用在写请求中。

uffer 和 Cache 分别缓存的是

对磁盘和文件系统的读写数据。从写的角度来说,不仅可以优化磁盘和文件的写入,对应用程序也有好处,应用程序可以在数据真正落盘前,就返回去做其他工作。

从读的角度来说,不仅可以提高那些频繁访问数据的读取速度,也降低了频繁 I/O 对磁盘的压力。

利用缓存的命中率来优化系统。

所谓缓存命中率,是指直接通过缓存获取数据的请求次数,占所有数据请求次数的百分比。

命中率越高,表示使用缓存带来的收益越高,应用程序的性能也就越好。实际上,缓存是现在所有高并发系统必需的核心模块,主要作用就是把经常访问的数据(也就是热点数据),提前读入到内存中。这样,下次访问时就可以直接从内存读取数据,而不需要经过硬盘,从而加快应用程序的响应速度。

这些独立的缓存模块通常会提供查询接口,方便随时查看缓存的命中情况。不过 Linux 系统中并没有直接提供这些接口,所以这里介绍一下,cachestat 和 cachetop  ,它们正是查看系统缓存命中情况的工具。

cachestat 提供了整个操作系统缓存的读写命中情况。

cachetop 提供了每个进程的缓存命中情况。

这两个工具都是 bcc 软件包的一部分,它们基于 Linux 内核的 eBPF(extended Berkeley Packet Filters)机制,来跟踪内核中管理的缓存,并输出缓存的使用和命中情况。

 bcc-tools 需要内核版本为 4.1 或者更新的版本,如果你用的是 CentOS,那就需要手动升级,但我升到5.8内核版本工具版本问题报错

安装

yum update
rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org && rpm -Uvh http://www.elrepo.org/elrepo-release-7.0-2.el7.elrepo.noarch.rpm
uname -r
yum remove kernel-headers kernel-tools kernel-tools-libs
yum -y install perl
yum --disablerepo="*" --enablerepo="elrepo-kernel" install kernel-lt kernel-lt-devel kernel-lt-headers kernel-lt-tools kernel-lt-tools-libs kernel-lt-tools-libs-devel
想要升级最新版本执行下面安装命令
yum --disablerepo="*" --enablerepo="elrepo-kernel" install kernel-ml kernel-ml-devel kernel-ml-headers kernel-ml-tools kernel-ml-tools-libs kernel-ml-tools-libs-devel

sed -i '/GRUB_DEFAULT/s/=.*/=0/' /etc/default/grub

grub2-mkconfig -o /boot/grub2/grub.cfg
reboot
uname -r  查看内核
4.4.233-1.el7.elrepo.x86_64
yum install -y bcc-tools 安装工具集
添加环境变量
echo 'export PATH=$PATH:/usr/share/bcc/tools' > /etc/profile.d/bcc-tools.sh
exec bash
[root@localhost ~]# cachestat 1 1
    HITS   MISSES  DIRTIES HITRATIO   BUFFERS_MB  CACHED_MB
       0        0        0    0.00%            2        302

  cachestat 的输出其实是一个表格。每行代表一组数据,而每一列代表不同的缓存统计指标。这些指标从左到右依次表示:

TOTAL ,表示总的 I/O 次数;

MISSES ,表示缓存未命中的次数;

HITS ,表示缓存命中的次数;

DIRTIES, 表示新增到缓存中的脏页数;

BUFFERS_MB 表示 Buffers 的大小,以 MB 为单位;

CACHED_MB 表示 Cache 的大小,以 MB 为单位。

再来看一个 cachetop 的运行界面:

$ cachetop
11:58:50 Buffers MB: 258 / Cached MB: 347 / Sort: HITS / Order: ascending
PID      UID      CMD              HITS     MISSES   DIRTIES  READ_HIT%  WRITE_HIT%
   13029 root     python                  1        0        0     100.0%       0.0%

  它的输出跟 top 类似,默认按照缓存的命中次数(HITS)排序,展示了每个进程的缓存命中情况。具体到每一个指标,这里的 HITS、MISSES 和 DIRTIES ,跟 cachestat 里的含义一样,分别代表间隔时间内的缓存命中次数、未命中次数以及新增到缓存中的脏页数。而 READ_HIT 和 WRITE_HIT ,分别表示读和写的缓存命中率。

指定文件的缓存大小

除了缓存的命中率外,还有一个指标也会很感兴趣,那就是指定文件在内存中的缓存大小。可以使用 pcstat 这个工具,来查看文件在内存中的缓存大小以及缓存比例。pcstat 是一个基于 Go 语言开发的工具,所以安装它之前,你首先应该安装 Go 语言,你可以点击这里下载安装。

cd /usr/bin
if [ $(uname -m) == "x86_64" ] ; then
    curl -L -o pcstat https://github.com/tobert/pcstat/raw/2014-05-02-01/pcstat.x86_64
else
    curl -L -o pcstat https://github.com/tobert/pcstat/raw/2014-05-02-01/pcstat.x86_32
fi
chmod 755 pcstat

  全部安装完成后,可以运行 pcstat 来查看文件的缓存情况了。比如,下面就是一个 pcstat 运行的示例,它展示了 /bin/ls 这个文件的缓存情况:

[root@localhost ~]# pcstat /bin/ls
|----------+----------------+------------+-----------+---------|
| Name     | Size           | Pages      | Cached    | Percent |
|----------+----------------+------------+-----------+---------|
| /bin/ls  | 117608         | 29         | 0         | 000.000 |
|----------+----------------+------------+-----------+---------|

  这个输出中,Cached 就是 /bin/ls 在缓存中的大小,而 Percent 则是缓存的百分比。如果看到它们都是 0,这说明 /bin/ls 并不在缓存中。

接着,如果执行一下 ls 命令,再运行相同的命令来查看的话,就会发现 /bin/ls 都在缓存中了:

[root@localhost ~]# ls
anaconda-ks.cfg  file
[root@localhost ~]# pcstat /bin/ls
|----------+----------------+------------+-----------+---------|
| Name     | Size           | Pages      | Cached    | Percent |
|----------+----------------+------------+-----------+---------|
| /bin/ls  | 117608         | 29         | 29        | 100.000 |
|----------+----------------+------------+-----------+---------|

 知道了缓存相应的指标和查看系统缓存的方法后

 

# 生成一个512MB的临时文件
#dd if=/dev/sda1 of=file bs=1M count=512
# 清理缓存
# echo 3 > /proc/sys/vm/drop_caches
# pcstat file
|----------+----------------+------------+-----------+---------|
| Name     | Size           | Pages      | Cached    | Percent |
|----------+----------------+------------+-----------+---------|
| file     | 536870912      | 131072     | 62974     | 048.045 |
|----------+----------------+------------+-----------+---------|

echo 3 > /proc/sys/vm/drop_caches
echo 3 > /proc/sys/vm/drop_caches
echo 3 > /proc/sys/vm/drop_caches
# pcstat file
|----------+----------------+------------+-----------+---------|
| Name     | Size           | Pages      | Cached    | Percent |
|----------+----------------+------------+-----------+---------|
| file     | 536870912      | 131072     | 0         | 000.000 |
|----------+----------------+------------+-----------+---------|

  运行 pcstat 命令,确认刚刚生成的文件不在缓存中。如果一切正常,看到 Cached 和 Percent 都是 0:,如果不是0多清理一下缓存

现在运行 cachetop 命令:

# 每隔5秒刷新一次数据
$ cachetop 5

  运行 dd 命令测试文件的读取速度:

[root@localhost ~]# dd if=file of=/dev/null bs=1M
记录了512+0 的读入
记录了512+0 的写出
536870912字节(537 MB)已复制,20.7171 秒,25.9 MB/秒

  从 dd 的结果可以看出,这个文件的读性能是 33.4 MB/s。由于在 dd 命令运行前我们已经清理了缓存,所以 dd 命令读取数据时,肯定要通过文件系统从磁盘中读取。

 查看 cachetop 界面的缓存命中情况 

07:57:55 Buffers MB: 0 / Cached MB: 288 / Sort: HITS / Order: ascending
PID      UID      CMD              HITS     MISSES   DIRTIES  READ_HIT%  WRITE_HIT%
    1409 root     cachetop                3        0        0     100.0%       0.0%
    1435 root     dd                  27648    27648        0	   50.0%      50.0%

  从 cachetop 的结果可以发现,并不是所有的读都落到了磁盘上,事实上读请求的缓存命中率只有 50% 。

继续尝试相同的测试命令。终端2再次执行刚才的 dd 命令

[root@localhost ~]# dd if=file of=/dev/null bs=1M
记录了512+0 的读入
记录了512+0 的写出
536870912字节(537 MB)已复制,0.123877 秒,4.3 GB/秒

  磁盘的读性能居然变成了 4.5 GB/s,比第一次的结果明显高了太多

看看 cachetop 的情况

08:03:35 Buffers MB: 0 / Cached MB: 635 / Sort: HITS / Order: ascending
PID      UID      CMD              HITS     MISSES   DIRTIES  READ_HIT%  WRITE_HIT%
    1409 root     cachetop                1        0        0     100.0%       0.0%
    1457 root     bash                  277        0        0     100.0%       0.0%
    1457 root     dd                 131644        0        0     100.0%       0.0%

  cachetop 也有了不小的变化。可以发现,这次的读的缓存命中率是 100.0%,也就是说这次的 dd 命令全部命中了缓存,所以才会看到那么高的性能。

终端2再次执行 pcstat 查看文件 file 的缓存情况

[root@localhost ~]# pcstat file
|----------+----------------+------------+-----------+---------|
| Name     | Size           | Pages      | Cached    | Percent |
|----------+----------------+------------+-----------+---------|
| file     | 536870912      | 131072     | 131072    | 100.000 |
|----------+----------------+------------+-----------+---------|

  pcstat 的结果可以发现,测试文件 file 已经被全部缓存了起来,这跟刚才观察到的缓存命中率 100% 是一致的。

这两次结果说明,系统缓存对第二次 dd 操作有明显的加速效果,可以大大提高文件读取的性能。但同时也要注意,如果把 dd 当成测试文件系统性能的工具,由于缓存的存在,就会导致测试结果严重失真。

再来看一个文件读写的案例

开启两个终端。分别 SSH 登录到机器上后,先在第一个终端中运行 cachetop 命令:

# 每隔5秒刷新一次数据
$ cachetop 5 

  接着,再到第二个终端,执行下面的命令运行案例:

docker run --privileged --name=app -itd feisky/app:io-direct

  查看环境是否启动完成

[root@localhost ~]# docker logs app
Reading data from disk /dev/sda2 with buffer size 33554432
Time used: 0.090524 s to read 33554432 bytes
Time used: 0.029526 s to read 33554432 bytes
Time used: 0.028942 s to read 33554432 bytes
Time used: 0.028966 s to read 33554432 bytes
Time used: 0.027196 s to read 33554432 bytes

  可以看到,每读取 32 MB 的数据,就需要花 0.9 秒

这个输出似乎有点意思了。1024 次缓存全部命中,读的命中率是 100%,看起来全部的读请求都经过了系统缓存。但是问题又来了,如果真的都是缓存 I/O,读取速度不应该这么慢。

08:19:57 Buffers MB: 0 / Cached MB: 959 / Sort: HITS / Order: ascending
PID      UID      CMD              HITS     MISSES   DIRTIES  READ_HIT%  WRITE_HIT%
    1409 root     cachetop                3        0        0     100.0%       0.0%
    1748 root     dockerd                10        0        5	   50.0%       0.0%
    1811 root     app                  2560        0        0     100.0%       0.0%

  每秒实际读取的数据大小。HITS 代表缓存的命中次数,那么每次命中能读取是一页数据。内存以页为单位进行管理,而每个页的大小是 4KB。所以,在 5 秒的时间间隔里,命中的缓存为 1024*4K/1024 = 4MB,再除以 5 秒,可以得到每秒读的缓存是 0.8MB,显然跟案例应用的 32 MB/s 相差太多。

如果为系统调用设置直接 I/O 的标志,就可以绕过系统缓存。那么,要判断应用程序是否用了直接 I/O,最简单的方法当然是观察它的系统调用,查找应用程序在调用它们时的选项。还是 strace。

[root@localhost ~]# strace -p $(pgrep app)
strace: Process 1811 attached
restart_syscall(<... resuming interrupted read ...>) = 0
openat(AT_FDCWD, "/dev/sda2", O_RDONLY|O_DIRECT) = 4
mmap(NULL, 33558528, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fbb65270000
read(4, ""..., 33554432) = 33554432
write(1, "Time used: 0.039126 s to read 33"..., 45) = 45
close(4)                                = 0
munmap(0x7fbb65270000, 33558528)        = 0
nanosleep({tv_sec=1, tv_nsec=0}, 0x7ffd1dcc01c0) = 0
openat(AT_FDCWD, "/dev/sda2", O_RDONLY|O_DIRECT) = 4
mmap(NULL, 33558528, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fbb65270000
read(4, ""..., 33554432) = 33554432
write(1, "Time used: 0.029105 s to read 33"..., 45) = 45
close(4)                                = 0

  从 strace 的结果可以看到,案例应用调用了 openat 来打开磁盘分区 /dev/sda2,并且传入的参数为 O_RDONLY|O_DIRECT(中间的竖线表示或)。O_RDONLY 表示以只读方式打开,而 O_DIRECT 则表示以直接读取的方式打开,这会绕过系统的缓存。

验证了这一点,就很容易理解为什么读 32 MB 的数据就都要那么久了。直接从磁盘读写的速度,自然远慢于对缓存的读写。这也是缓存存在的最大意义了。

对代码做修改重新运行

[root@localhost ~]# docker rm -f app
app
[root@localhost ~]#  docker run --privileged --name=app -itd feisky/app:io-cached
Unable to find image 'feisky/app:io-cached' locally
io-cached: Pulling from feisky/app
32802c0cfa4d: Already exists 
da1315cffa03: Already exists 
fa83472a3562: Already exists 
f85999a86bef: Already exists 
2f251909225c: Retrying in 1 second 
a374aef23781: Downloading 
io-cached: Pulling from feisky/app
32802c0cfa4d: Already exists 
da1315cffa03: Already exists 
fa83472a3562: Already exists 
f85999a86bef: Already exists 
2f251909225c: Pull complete 
a374aef23781: Pull complete 
Digest: sha256:affc2e9dd8d4cecc23b918e7b536852c747ce86291eb4daecdc8903b16c461ed
Status: Downloaded newer image for feisky/app:io-cached
5843d1ee9bf07381fa81acf834d4f37dd56f77d08dab3bfd5e31d7301c6a514c
[root@localhost ~]# docker logs app
Reading data from disk /dev/sda2 with buffer size 33554432
Time used: 0.030117 s to read 33554432 bytes
Time used: 0.027294 s to read 33554432 bytes
Time used: 0.025451 s to read 33554432 bytes
Time used: 0.052026 s to read 33554432 bytes
Time used: 0.026425 s to read 33554432 bytes
Time used: 0.019488 s to read 33554432 bytes
Time used: 0.025104 s to read 33554432 bytes
Time used: 0.024904 s to read 33554432 bytes
Time used: 0.025110 s to read 33554432 bytes
Time used: 0.025644 s to read 33554432 bytes
Time used: 0.025669 s to read 33554432 bytes
Time used: 0.023755 s to read 33554432 bytes
Time used: 0.022087 s to read 33554432 bytes
Time used: 0.023663 s to read 33554432 bytes
Time used: 0.024177 s to read 33554432 bytes
Time used: 0.025311 s to read 33554432 bytes
Time used: 0.014088 s to read 33554432 bytes
Time used: 0.021050 s to read 33554432 bytes
Time used: 0.024807 s to read 33554432 bytes
Time used: 0.025297 s to read 33554432 bytes
Time used: 0.024300 s to read 33554432 bytes
Time used: 0.024217 s to read 33554432 bytes
Time used: 0.024323 s to read 33554432 bytes
Time used: 0.024950 s to read 33554432 bytes

  现在,每次只需要 0.03 秒,就可以读取 32MB 数据,明显比之前的 0.9 秒快多了。所以,这次应该用了系统缓存。

查看 cachetop 的输出来确认一下

08:35:49 Buffers MB: 36 / Cached MB: 1126 / Sort: HITS / Order: ascending
PID      UID      CMD              HITS     MISSES   DIRTIES  READ_HIT%  WRITE_HIT%
    1409 root     cachetop                3        0        0     100.0%       0.0%
    1590 root     dockerd                10        1        5	   45.5%       0.0%
    2164 root     app                 40960        0        0     100.0%       0.0%

  果然,读的命中率还是 100%,HITS (即命中数)却变成了 40960,同样的方法计算一下,换算成每秒字节数正好是 32 MB(即 40960*4k/5/1024=32M)。这个说明,在进行 I/O 操作时,充分利用系统缓存可以极大地提升性能。 但在观察缓存命中率时,还要注意结合应用程序实际的 I/O 大小,综合分析缓存的使用情况。

cachestat 和 cachetop 这两个工具,观察系统和进程的缓存命中情况。

其中,cachestat 提供了整个系统缓存的读写命中情况。

cachetop 提供了每个进程的缓存命中情况。

原文地址:https://www.cnblogs.com/rdchenxi/p/13569357.html