Linux 里Buffer和Cache的定义及使用

摘要:
大部分指标都比较容易理解,但Buffer和Cache可能不太好区分。uffer和Cache分别缓存的是对磁盘和文件系统的读写数据。不过Linux系统中并没有直接提供这些接口,所以这里介绍一下,cachestat和cachetop,它们正是查看系统缓存命中情况的工具。这两个工具都是bcc软件包的一部分,它们基于Linux内核的eBPF机制,来跟踪内核中管理的缓存,并输出缓存的使用和命中情况。

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, "

免责声明:内容来源于网络,仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇WPF中如何为ItemsControl添加ScrollViewer并显示ScrollBarUnity3D 碰撞关系表 (转)下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

linux服务器挂掉自动重启脚本(转)

实现原理主要是使用linux提供的crontab机制,定时查询服务器进程是否存在,如果宕机则执行我们预设的重启脚本。 首先我们要向crontab加入一个新任务 sudo crontab -e #进入编辑状态,选用vi编辑。 */1 * * * * sh /root/monitor.sh #添加每分钟检测 我这里只是简单的设置每分钟调用一个shell脚本...

Linux系统学习 十八、VSFTP服务—虚拟用户访问—配置虚拟用户访问

配置虚拟用户访问 首先至少要关闭userlist 改完配置文件是要重启服务来使它生效 其实在刚装好vsftp的时候的配置文件不用修改的情况下配置虚拟用户访问控制是最好的 local_root选项不影响 本地用户登录的目录和虚拟用户登录的目录是不产生影响的 为防止有影响,把chroot也注释了 配置虚拟用户登录的步骤: 1、添加虚拟用户口令文件 2、...

linux下使用source insight

以前都在Windows下用source insight,但是最近需要在Linux下使用,幸好知道有wine这个东西。多知道一些东西还是有意义的。 安装: 1. sudo apt-get install wine 2.将source insight拷贝到Linux中 3.进入Linux中InsightSetup.exe所在目录,使用wine安装sou...

Linux通过nginx反向代理net core项目

如果想了解nginx是干嘛的,可以参考上篇博文正向代理和反向代理 一、安装配置nginx 1、使用xshell登录我们的Linux系统 2、安装make,输入如下命令 yum -y install gcc automake autoconf libtool make 3、安装g++环境,输入如下命令 yum install gcc gcc-c++ 4、...

linux进程状态详解(转)

  Linux是一个多用户,多任务的系统,可以同时运行多个用户的多个程序,就必然会产生很多的进程,而每个进程会有不同的状态。  在下文将对进程的 R、S、D、T、Z、X 六种状态做个说明。 PROCESS STATE CODES        Here are the different values that the s, stat and state...

linux 3.10 的中断收包笔记

来看下NAPI和非NAPI的区别: (1) 支持NAPI的网卡驱动必须提供轮询方法poll()。 (2) 非NAPI的内核接口为netif_rx(),NAPI的内核接口为napi_schedule(),或者类似的__napi_schedule之类的,总之都是在硬中断中调用对应的函数。 (3) 非NAPI使用共享的CPU队列softnet_data->...