重新认识 free 命令
$ free -h
total used free shared buff/cache available
Mem: 7.4Gi 243Mi 7.0Gi 2.0Mi 130Mi 6.9Gi
Swap: 0B 0B 0B
- total: 系统所有的物理内存.
- used: 系统已经使用的物理内存.
- free: 系统剩余的物理内存.
- shared: 多个进程共享的内存总额.
- buff/cache: 缓存使用的物理内存.
- available: 系统可用的物理内存, 包括 free + 可释放的缓存使用的物理内存.
buffer和cache数据的来源
$ 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)
- Buffers 是内核缓冲区用到的内存,对应的是 /proc/meminfo 中的 Buffers 值。
- Cache 是内核页缓存和 Slab 用到的内存,对应的是 /proc/meminfo 中的 Cached 与 SReclaimable 之和。
查看 proc 的详细文档
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、Cached描述的用词, Buffer着重说的是 disk blocks, 而 Cache着重说的是 files. 但是没有说明以下两个问题:
- Buffer 只会缓存磁盘块的写入吗, 读取数据会缓存吗?
- Cache 只会缓存文件的读吗, 写入数据会缓存吗?
先说结论:
- Buffer: 对文件系统上的文件进行读写, 会使用 Buffer 缓存.
- Cache: 对磁盘进行直接读写, 会使用 Cache 缓存.
案例前的准备:
- 安装: apt install sysstat
- 机器配置: 2 CPU,8GB 内存
- 清理文件页、目录项、Inodes等各种缓存:
echo 3 > /proc/sys/vm/drop_caches
在第一个终端中执行:
$ vmstat 1
在第二个终端执行dd 命令,通过读取随机设备,生成一个 1G 大小的文件:
$ dd if=/dev/urandom of=/tmp/file bs=1M count=1000
回到第一个终端观察窗口:
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
3 0 0 6246 2 1427 0 0 514 26725 603 991 2 14 59 25 0
1 1 0 6066 2 1606 0 0 4 115316 1653 2381 1 38 14 47 0
0 4 0 5854 2 1817 0 0 8 172172 1418 1990 1 47 6 46 0
1 2 0 5803 2 1868 0 0 4 65724 1209 1767 1 13 36 51 0
8 1 0 5770 2 1900 0 0 4 139324 1166 1642 1 9 0 90 0
...
0 2 0 457 5 7217 0 0 8 115496 1503 2188 1 34 11 53 0
0 2 0 327 5 7347 0 0 8 115040 1359 2007 2 30 22 48 0
0 2 0 175 5 7498 0 0 4 123236 1426 1968 1 34 16 49 0
0 4 0 143 5 7531 0 0 0 123228 1446 2181 1 33 0 66 0
0 1 0 125 5 7550 0 0 8 123100 1386 2062 1 21 6 73 0
1 1 0 141 5 7535 0 0 0 82572 1467 2249 1 24 20 55 0
0 4 0 132 5 7544 0 0 4 131180 1384 1915 1 47 8 45 0
1 2 0 144 5 7531 0 0 4 131268 1161 1696 1 14 25 61 0
...
0 3 0 134 5 7542 0 0 4 49248 1296 1833 0 31 7 63 0
0 3 0 140 5 7535 0 0 4 114876 1305 1907 1 21 4 75 0
通过观察 vmstat 的输出,我们发现,在 dd 命令运行时, Cache 在不停地增长,而 Buffer 基本保持不变。再进一步观察 I/O 的情况,你会看到,
- 在 Cache 刚开始增长时,块设备 I/O 很少,bi 只出现了几次 8 KB/s,bo 则只有一次 26725KB。而过一段时间后,才会出现大量的块设备写,比如 bo 变成了 139324。
- 当 dd 命令结束后,Cache 不再增长,但块设备写还会持续一段时间,并且,多次 I/O 写的结果加起来,才是 dd 要写的 1G 的数据。
下面的命令对环境要求很高,需要你的系统配置多块磁盘,并且磁盘分区 /dev/vda3 还要处于未使用状态。如果你只有一块磁盘,千万不要尝试,否则将会对你的磁盘分区造成损坏。
在第二个终端执行:
# 首先清理缓存
$ echo 3 > /proc/sys/vm/drop_caches
# 然后运行dd命令向磁盘分区/dev/sdb1写入2G数据
$ dd if=/dev/urandom of=/dev/vda3 bs=1M count=2048
回到第一个终端观察内存和 I/O 的变化情况:
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
0 0 0 7544 4 134 0 0 0 532 987 1737 1 0 99 1 0
1 0 0 7359 185 139 0 0 72 128 1189 8470 2 42 56 0 0
2 0 0 7095 443 146 0 0 0 0 1095 1557 1 51 49 0 0
1 0 0 6832 699 153 0 0 0 1712 1360 1940 1 51 48 1 0
1 0 0 6568 955 160 0 0 0 0 1247 1697 1 51 49 0 0
1 0 0 6303 1211 167 0 0 0 0 1280 1794 0 51 49 0 0
2 0 0 6054 1453 174 0 0 0 215752 4700 2440 0 56 10 34 0
1 1 0 5805 1694 181 0 0 0 112840 3078 4531 2 55 1 42 0
1 1 0 5548 1942 188 0 0 0 113664 2878 1639 1 52 0 47 0
0 1 0 5332 2151 194 0 0 244 113428 3185 2640 3 47 6 44 0
2 0 0 5326 2151 194 0 0 0 113444 2564 1623 0 3 49 49 0
0 1 0 5325 2151 194 0 0 0 110816 2546 1596 1 2 49 49 0
从这里你会看到,虽然同是写数据,写磁盘跟写文件的现象还是不同的。写磁盘时(也就是 bo 大于 0 时),Buffer 和 Cache 都在增长,但显然 Buffer 的增长快得多。这说明,写磁盘用到了大量的 Buffer,这跟我们在文档中查到的定义是一样的。对比两个案例,我们发现,写文件时会用到 Cache 缓存数据,而写磁盘则会用到 Buffer 来缓存数据。所以,回到刚刚的问题,虽然文档上只提到,Cache 是文件读的缓存,但实际上,Cache 也会缓存写文件时的数据。
回到第二个终端,运行下面的命令。清理缓存后,从文件 /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 0 0 7521 3 158 0 0 1043 4461 524 876 4 5 89 3 0
0 0 0 7521 3 158 0 0 0 0 763 1423 0 1 100 0 0
0 0 0 7521 0 150 0 0 20 0 831 1530 1 1 99 0 0
0 0 0 7521 0 150 0 0 0 0 776 1467 0 1 100 0 0
2 0 0 7451 1 242 0 0 90872 0 2015 1532 17 19 64 1 0
3 0 0 7326 1 368 0 0 123560 0 1363 1552 24 28 48 1 0
1 0 0 7199 1 494 0 0 122880 0 1088 1410 25 26 49 0 0
1 0 0 7084 2 620 0 0 122996 9196 1293 1470 24 27 49 0 0
1 0 0 6962 2 742 0 0 118784 0 1162 1436 26 26 49 0 0
1 0 0 6841 2 864 0 0 118784 0 1103 1388 24 28 49 0 0
1 0 0 6719 2 986 0 0 118784 0 1094 1377 27 23 50 0 0
1 0 0 6596 2 1108 0 0 119464 0 1148 1453 22 29 49 1 0
0 0 0 6503 3 1200 0 0 90644 0 1105 1441 19 23 59 0 0
观察 vmstat 的输出,你会发现读取文件时(也就是 bi 大于 0 时),Buffer 保持不变,而 Cache 则在不停增长。这跟我们查到的定义“Cache 是对文件读的页缓存”是一致的。
第二个终端,运行下面的命令。清理缓存后,从磁盘分区 /dev/vda3 中读取数据,写入空设备:
# 首先清理缓存
$ echo 3 > /proc/sys/vm/drop_caches
# 运行dd命令读取文件
$ dd if=/dev/vda3 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 7547 1 135 0 0 2467 2708 478 816 3 4 91 2 0
0 0 0 7547 1 135 0 0 0 0 785 1435 0 0 100 0 0
0 0 0 7547 1 135 0 0 80 32 820 1464 1 0 99 0 0
0 0 0 7547 1 135 0 0 0 0 779 1449 1 1 99 0 0
0 0 0 7547 1 135 0 0 0 0 778 1455 0 1 100 0 0
0 0 0 7547 1 135 0 0 0 0 820 1519 0 0 100 0 0
0 0 0 7547 1 135 0 0 0 0 800 1490 0 1 100 0 0
0 0 0 7547 1 135 0 0 0 0 822 1512 0 1 100 0 0
1 0 0 7352 196 135 0 0 190328 0 1109 1567 1 6 72 22 0
0 1 0 7234 313 135 0 0 114688 0 916 1518 0 6 49 46 0
1 0 0 7120 428 135 0 0 110592 0 924 1481 0 5 49 46 0
0 1 0 7004 544 135 0 0 114688 0 883 1492 0 4 50 46 0
1 0 0 6890 658 136 0 0 110592 0 874 1456 1 3 50 46 0
0 1 0 6773 775 136 0 0 114688 0 925 1540 1 4 50 47 0
1 0 0 6658 890 136 0 0 110592 0 905 1454 1 3 50 46 0
0 1 0 6542 1005 136 0 0 114688 0 913 1461 1 4 50 46 0
0 0 0 6466 1081 137 0 0 73728 0 909 1483 0 3 67 31 0
观察 vmstat 的输出,你会发现读磁盘时(也就是 bi 大于 0 时),Buffer 和 Cache 都在增长,但显然 Buffer 的增长快很多。这说明读磁盘时,数据缓存到了 Buffer 中。
- Buffer 既可以用作
将要写入磁盘数据的缓存
,也可以用作从磁盘读取数据的缓存
。 - Cache 既可以用作
从文件读取数据的页缓存
,也可以用作写文件的页缓存
。
简单来说,Buffer 是对磁盘数据的缓存,而 Cache 是文件数据的缓存,它们既会用在读请求中,也会用在写请求中。
- hcache: 可以查看cache的使用情况, 比如列出topN, 列出指定进程的cache使用情况
- vmtouch: 统计出某个文件、目录,甚至是嵌套目录下各文件真正使用cache的大小,从而明确cache消耗的分布情况。
wget https://silenceshell-1255345740.cos.ap-shanghai.myqcloud.com/hcache -O /usr/local/bin/hcache
chmod +x /usr/local/bin/hcache
hcache 参数解释
$ hcache --top 10
+------------------------------------------------------------------+----------------+------------+-----------+---------+
| Name | Size (bytes) | Pages | Cached | Percent |
|------------------------------------------------------------------+----------------+------------+-----------+---------|
| /usr/bin/dockerd | 99860032 | 24380 | 16907 | 069.348 |
| /usr/bin/containerd | 52267080 | 12761 | 10034 | 078.630 |
...
- Name: 占用缓存的文件全路径
- Size: 文件大小
- Pages: 占了多少个Block
- Cached: 缓存使用了多少个Block
- Percent: Cached 与 Pages的比值
测试准备: 在第一个窗口执行dd 命令,通过读取随机设备,生成一个 30G 大小的文件:
$ dd if=/dev/urandom of=/tmp/file bs=1M count=10000
在第一个窗口运行 dd 命令测试文件的读取速度:
$ dd if=file of=/dev/null bs=1M
apt install vmtouch
安装 cachestat 和 cachetop, 在这之前,我们首先要安装 bcc 软件包。比如,在 Ubuntu 系统中,你可以运行下面的命令来安装:
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 4052245BD4284CDD
echo "deb https://repo.iovisor.org/apt/xenial xenial main" | sudo tee /etc/apt/sources.list.d/iovisor.list
sudo apt-get update
sudo apt-get install -y bcc-tools libbcc-examples linux-headers-$(uname -r)
注意:bcc-tools 需要内核版本为 4.1 或者更新的版本,如果你用的是 CentOS,那就需要手动升级内核版本后再安装。
操作完这些步骤,bcc 提供的所有工具就都安装到 /usr/share/bcc/tools 这个目录中了。不过这里提醒你,bcc 软件包默认不会把这些工具配置到系统的 PATH 路径中,所以你得自己手动配置:
$ export PATH=$PATH:/usr/share/bcc/tools
配置完,你就可以运行 cachestat 和 cachetop 命令了。比如,下面就是一个 cachestat 的运行界面,它以 1 秒的时间间隔,输出了 3 组缓存统计数据:
$ cachestat 1 3
TOTAL MISSES HITS DIRTIES BUFFERS_MB CACHED_MB
2 0 2 1 17 279
2 0 2 1 17 279
2 0 2 1 17 279
你可以看到,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 ,分别表示读和写的缓存命中率。