使用 free 查看查看内存使用情况
# free // 以字节为单位 total used free shared buff/cache available Mem: 16108680 13563160 301692 731132 2243828 1478772 Swap: 31457276 16751336 14705940 // 1)total,总内存大小; // 2)used,已使用内存的大小,包含了共享内存; // 3)free,未使用内存的大小; // 4)shared,共享内存的大小; // 5)buff/cache,缓存和缓冲区的大小; // 6)available,新进程可用内存的大小;包含未使用内存,还包括了可回收的缓存,会比未使用内存更大。但并不是所有缓存都可以回收,因为有些缓存可能正在使用。
命令 free 从 /proc/meminfo 读取数据,根据 man proc 手册:
buffer
1)Buffers,内核缓冲区用到的内存,对应的是 /proc/meminfo 中的 Buffers 值;
Buffers,对原始磁盘块的临时存储,也就是用来缓存磁盘的数据,通常不会特别大(20MB 左右)。这样,内核就可以把分散的写集中起来,统一优化磁盘的写入,比如可以把多次小的写合并成单次大的写等等。
cache
2)Cache,内核页缓存和 Slab 用到的内存,对应的是 /proc/meminfo 中的 Cached 与 SReclaimable 之和;
Cached,从磁盘读取文件的页缓存,也就是用来缓存从文件读取的数据。
SReclaimable,Slab 的一部分。Slab 包括两部分,其中的可回收部分,用 SReclaimable 记录;而不可回收部分,用 SUnreclaim 记录。
概念理解:Buffer and Cache
Buffer 既可以用作“将要写入磁盘数据的缓存”,也可以用作“从磁盘读取数据的缓存”。Cache 既可以用作“从文件读取数据的页缓存”,也可以用作“写文件的页缓存”。
简单来说,Buffer 是对磁盘数据的缓存,而 Cache 是文件数据的缓存,它们既会用在读请求中,也会用在写请求中。
在读写文件时,会经过文件系统,由文件系统负责与磁盘交互;在读写磁盘时,就会跳过文件系统,也就是所谓的“裸I/O“。这两种读写方式所使用的缓存是不同的,也就是 Cache 和 Buffer 区别。
实验探究:文件写入 与 磁盘写入
写文件时会用到 Cache 缓存数据,而写磁盘则会用到 Buffer 来缓存数据。虽然文档上只提到,Cache 是文件读的缓存,但实际上,Cache 也会缓存写文件时的数据。
文件写入:
# echo 3 > /proc/sys/vm/drop_caches # vmstat 1 // 每隔1秒输出1组数据 # dd if=/dev/urandom of=/tmp/file bs=1M count=500 # vmstat 1 // 内存部分的 buff 和 cache ,以及 io 部分的 bi 和 bo 要重点关注 // 在命令运行时,Buffer 基本保持不变,Cache 在不停地增长,块设备 I/O 很少,bi 只出现了一次 488 KB/s,bo 则只有一次 4KB // 而过一段时间后,才会出现大量的块设备写,比如 bo 变成 122880 // 当命令结束后,Cache 不再增长,但块设备写还会持续一段时间,并且,多次 I/O 写的结果加起来,才是 dd 要写的 500M 的数据 // 这与文档描述冲突,文档说:Cache 是磁盘读取文件时的缓存,为什么写入操作会涉及 Cache?
磁盘写入:
# echo 3 > /proc/sys/vm/drop_caches # vmstat 1 // 每隔1秒输出1组数据 # dd if=/dev/urandom of=/dev/sdb1 bs=1M count=2048 # vmstat 1 // 写磁盘时(也就是 bo 大于 0 时),Buffer 和 Cache 都在增长,但显然 Buffer 的增长快得多 // 写磁盘用到了大量的 Buffer,这跟我们在文档中查到的定义是一样的
实验探究:文件读取 与 磁盘读取
读文件时数据会缓存到 Cache 中,而读磁盘时数据会缓存到 Buffer 中。
文件读取:
# echo 3 > /proc/sys/vm/drop_caches # vmstat 1 // 每隔1秒输出1组数据 # dd if=/tmp/file of=/dev/null # vmstat 1 // 读取文件时(也就是 bi 大于 0 时),Buffer 保持不变,而 Cache 则在不停增长 // 这跟我们查到的定义“Cache 是对文件读的页缓存”是一致的
磁盘读取:
# echo 3 > /proc/sys/vm/drop_caches # vmstat 1 // 每隔1秒输出1组数据 # dd if=/dev/sda1 of=/dev/null bs=1M count=1024 # vmstat 1 // 读磁盘时(也就是 bi 大于 0 时),Buffer 和 Cache 都在增长,但显然 Buffer 的增长快很多。这说明读磁盘时,数据缓存到了 Buffer 中 // 这跟我们查到的定义“Cache 是对文件读的页缓存”是一致的
缓存命中率 – 衡量缓存使用的好坏
缓存命中率,是指直接通过缓存获取数据的请求次数占所有数据请求次数的百分比。命中率越高,表示使用缓存带来的收益越高,应用程序的性能也就越好。
常用缓存命中率查看工具
使用如下工具,以查看系统缓存命中情况的工具,属于 bcc 软件包,基于 Linux 内核的 eBPF(extended Berkeley Packet Filters)机制,Kernel >= 4.1:
1)cachestat,整个操作系统缓存的读写命中情况
2)cachetop,每个进程的缓存命中情况
cachestat
# cachestat-bpfcc 1 3 // 以 1 秒的时间间隔,输出了 3 组缓存统计数据 TOTAL MISSES HITS DIRTIES BUFFERS_MB CACHED_MB 29408 29 29379 102 21 1032 57596 12265 45331 68 20 1035 117526 7088 110438 316 21 1007 // TOTAL ,表示总的 I/O 次数; = MISSES + HITS // MISSES ,表示缓存未命中的次数; // HITS ,表示缓存命中的次数; // DIRTIES, 表示新增到缓存中的脏页数; // BUFFERS_MB 表示 Buffers 的大小,以 MB 为单位; // CACHED_MB 表示 Cache 的大小,以 MB 为单位;
cachetop
08:56:03 Buffers MB: 54 / Cached MB: 1400 / Sort: HITS / Order: ascending PID UID CMD HITS MISSES DIRTIES READ_HIT% WRITE_HIT% 22894 k4nz thunderbird-bin 0 72 0 0.0% 100.0% 23595 k4nz Socket Thread 0 128 0 0.0% 100.0% 12196 k4nz chrome 1 0 0 100.0% 0.0% 14483 k4nz Chrome_ChildIOT 1 0 0 100.0% 0.0% 1841 libvirt- CPU 1/KVM 1 0 0 100.0% 0.0% 25951 k4nz DNS Res~r #2078 1 0 0 100.0% 0.0% // 输出跟 top 类似,默认按照缓存的命中次数(HITS)排序,示了每个进程的缓存命中情况 // 具体到每一个指标,跟 cachestat 里的含义一样 // READ_HIT% 和 WRITE_HIT% ,分别表示读和写的缓存命中率
pcstat – 特定文件的以缓存大小
# pcstat /bin/tail +-----------+----------------+------------+-----------+---------+ | Name | Size (bytes) | Pages | Cached | Percent | |-----------+----------------+------------+-----------+---------| | /bin/tail | 72608 | 18 | 0 | 000.000 | +-----------+----------------+------------+-----------+---------+ # tail -n 1 /var/log/syslog Aug 4 18:56:13 localhost log.example.com.sh[23480] 10.10.12.122 # pcstat /bin/tail +-----------+----------------+------------+-----------+---------+ | Name | Size (bytes) | Pages | Cached | Percent | |-----------+----------------+------------+-----------+---------| | /bin/tail | 72608 | 18 | 18 | 100.000 | +-----------+----------------+------------+-----------+---------+
实验探究:系统缓存对文件读取性能的影响
# dd if=/dev/sda1 of=file bs=1M count=512 # echo 3 > /proc/sys/vm/drop_caches # pcstat ./file +--------+----------------+------------+-----------+---------+ | Name | Size (bytes) | Pages | Cached | Percent | |--------+----------------+------------+-----------+---------| | ./file | 536870912 | 131072 | 0 | 000.000 | +--------+----------------+------------+-----------+---------+ # dd if=file of=/dev/null bs=1M 512+0 records in 512+0 records out 536870912 bytes (537 MB, 512 MiB) copied, 4.85321 s, 111 MB/s root@laptop /mnt/k4nz // 当进行上述操作之后,./file 会被缓存,但是缓存的程度可能不同。 // 虽然我们已经清理了缓存,但是操作系统会预读,导致上述 dd 命令会使用缓存。这点可以通过 cachetop 观察到命中率 // 最然会绕过系统缓存,但是还存在元数据缓存,所以即使绕过缓存也会存在缓存命中率 # pcstat ./file +--------+----------------+------------+-----------+---------+ | Name | Size (bytes) | Pages | Cached | Percent | |--------+----------------+------------+-----------+---------| | ./file | 536870912 | 131072 | 131072 | 100.000 | +--------+----------------+------------+-----------+---------+ # dd if=file of=/dev/null bs=1M 512+0 records in 512+0 records out 536870912 bytes (537 MB, 512 MiB) copied, 0.171562 s, 3.1 GB/s // 系统缓存对第二次 dd 操作有明显的加速效果,可以大大提高文件读取的性能。 // 但同时也要注意,如果我们把 dd 当成测试文件系统性能的工具,由于缓存的存在,就会导致测试结果严重失真
实验探究:直接 I/O 对文件读取性能的影响
# cachetop 5 # docker run --privileged --name=app-io-direct -itd feisky/app:io-direct // 每秒从磁盘分区 /dev/sda1 中读取 32MB 的数据 # docker logs app // 确认案例已经正常启动 // 每读取 32 MB 的数据,就需要花 0.9 秒,这个速度并不高 # cachetop 5 16:39:18 Buffers MB: 73 / Cached MB: 281 / Sort: HITS / Order: ascending PID UID CMD HITS MISSES DIRTIES READ_HIT% WRITE_HIT% 21881 root app 1024 0 0 100.0% 0.0% // 缓存全部命令中,但是大小只有 1024 * 4K / 1024K/M / 5s = 0.8M/s,也就是说秒读取 0.8M 缓存 // 可以推测系统缓存没有充分利用,类似于我们之前 DIRECT_IO 例子 // 查看系统调用以验证猜想 # strace -p $(pgrep app) // 从 strace 的结果可以看到,案例应用调用 openat 来打开磁盘分区,并且传入的参数为 O_RDONLY|O_DIRECT // 而 O_DIRECT 这会绕过系统的缓存 // 另外,cachetop 工具并不把直接 I/O 算进来,所以通过 cachetop 只能看到很少一部分数据的全部命中 // 毕竟 iotop 计算缓存命中,而不是所有读取次数
参考文献
16 | 基础篇:怎么理解内存中的Buffer和Cache?