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, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 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, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 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 提供了每個進程的緩存命中情況。