Skip to content

Latest commit

 

History

History
305 lines (245 loc) · 15.7 KB

内存性能.md

File metadata and controls

305 lines (245 loc) · 15.7 KB

一、Linux内存工作方式

二、Buffer 和 Cache

重新认识 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. 但是没有说明以下两个问题:

  1. Buffer 只会缓存磁盘块的写入吗, 读取数据会缓存吗?
  2. Cache 只会缓存文件的读吗, 写入数据会缓存吗?

先说结论:

  • Buffer: 对文件系统上的文件进行读写, 会使用 Buffer 缓存.
  • Cache: 对磁盘进行直接读写, 会使用 Cache 缓存.

案例前的准备:

  • 安装: apt install sysstat
  • 机器配置: 2 CPU,8GB 内存
  • 清理文件页、目录项、Inodes等各种缓存: echo 3 > /proc/sys/vm/drop_caches

2.1 场景 1:磁盘和文件写案例

2.1.1 文件写案例

在第一个终端中执行:

$ 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 的数据。

2.1.1 磁盘写案例

下面的命令对环境要求很高,需要你的系统配置多块磁盘,并且磁盘分区 /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 也会缓存写文件时的数据。

2.2 场景 2:磁盘和文件读案例

2.2.1 文件读案例

回到第二个终端,运行下面的命令。清理缓存后,从文件 /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 是对文件读的页缓存”是一致的。

2.2.2 磁盘读案例

第二个终端,运行下面的命令。清理缓存后,从磁盘分区 /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 是文件数据的缓存,它们既会用在读请求中,也会用在写请求中。

2.3 查看 Buffer、Cache 占用情况

  • hcache: 可以查看cache的使用情况, 比如列出topN, 列出指定进程的cache使用情况
  • vmtouch: 统计出某个文件、目录,甚至是嵌套目录下各文件真正使用cache的大小,从而明确cache消耗的分布情况。

2.3.1 hcache

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

2.3.2 vmtouch

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 ,分别表示读和写的缓存命中率。

四、内存泄露

五、swap 变高

六、寻找系统的内存问题