gitbook/Linux内核技术实战课/docs/273892.md
2022-09-03 22:05:03 +08:00

163 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 01 基础篇 | 如何用数据观测Page Cache
你好我是邵亚方。今天我想和你聊一聊Page Cache的话题。
Page Cache你应该不陌生了如果你是一名应用开发者或者Linux运维人员那么在工作中你可能遇见过与Page Cache有关的场景比如
* 服务器的load飙高
* 服务器的I/O吞吐飙高
* 业务响应时延出现大的毛刺;
* 业务平均访问时延明显增加。
这些问题很可能是由于Page Cache管理不到位引起的因为Page Cache管理不当除了会增加系统I/O吞吐外还会引起业务性能抖动我在生产环境上处理过很多这类问题。
据我观察这类问题出现后业务开发人员以及运维人员往往会束手无策究其原因在于他们对Page Cache的理解仅仅停留在概念上并不清楚Page Cache如何和应用、系统关联起来对它引发的问题自然会束手无策了。所以要想不再踩Page Cache的坑你必须对它有个清晰的认识。
那么在我看来认识Page Cache最简单的方式就是用数据说话通过具体的数据你会更加深入地理解Page Cache的本质。为了帮你消化和理解我会用两节课的时间用数据剖析什么是Page Cache为什么需要Page CachePage Cache的产生和回收是什么样的。这样一来你会从本质到表象透彻理解它深切感受它和你的应用程序之间的关系从而能更好地理解上面提到的四个问题。
不过在这里我想给你提个醒要学习今天的内容你最好具备一些Linux编程的基础比如如何打开一个文件如何读写一个文件如何关闭一个文件等等。这样你理解今天的内容会更加容易当然了不具备也没有关系如果遇到你实在看不懂的地方你可以查阅《UNIX环境高级编程》这本书它是每一位Linux开发者以及运维人员必看的入门书籍。
好了,话不多说,我们进入今天的学习。
## 什么是Page Cache
我记得很多应用开发者或者运维在向我寻求帮助解决Page Cache引起的问题时总是喜欢问我Page Cache到底是属于内核还是属于用户针对这样的问题我一般会让他们先看下面这张图
![](https://static001.geekbang.org/resource/image/f3/1b/f344917f3cacd5bc06ae7c743a217f1b.png?wh=2860*2440 "应用程序产生Page Cache的逻辑示意图 ")
通过这张图片你可以清楚地看到红色的地方就是Page Cache**很明显Page Cache是内核管理的内存也就是说它属于内核不属于用户。**
那咱们怎么来观察Page Cache呢其实在Linux上直接查看Page Cache的方式有很多包括/proc/meminfo、free 、/proc/vmstat命令等它们的内容其实是一致的。
我们拿/proc/meminfo命令举例看一下如果你想了解/proc/meminfo中每一项具体含义的话可以去看[Kernel Documentation](https://www.kernel.org/doc/Documentation/filesystems/proc.rst)的meminfo这一节它详细解释了每一项的具体含义Kernel Documentation是应用开发者想要了解内核最简单、直接的方式
```
$ cat /proc/meminfo
...
Buffers: 1224 kB
Cached: 111472 kB
SwapCached: 36364 kB
Active: 6224232 kB
Inactive: 979432 kB
Active(anon): 6173036 kB
Inactive(anon): 927932 kB
Active(file): 51196 kB
Inactive(file): 51500 kB
...
Shmem: 10000 kB
...
SReclaimable: 43532 kB
...
```
根据上面的数据你可以简单得出这样的公式等式两边之和都是112696 KB
> Buffers + Cached + SwapCached = Active(file) + Inactive(file) + Shmem + SwapCached
**那么等式两边的内容就是我们平时说的Page Cache。**请注意你没有看错两边都有SwapCached之所以要把它放在等式里就是说它也是Page Cache的一部分。
接下来我带你分析一下这些项的具体含义。等式右边这些项把Buffers和Cached做了一下细分分为了Active(file)Inactive(file) 和Shmem因为Buffers更加依赖于内核实现在不同内核版本中它的含义可能有些不一致而等式右边和应用程序的关系更加直接所以我们从等式右边来分析。
在Page Cache中Active(file)+Inactive(file)是File-backed page与文件对应的内存页是你最需要关注的部分。因为你平时用的mmap()内存映射方式和buffered I/O来消耗的内存就属于这部分**最重要的是,这部分在真实的生产环境上也最容易产生问题,**我们在接下来的课程案例篇会重点分析它。
而SwapCached是在打开了Swap分区后把Inactive(anon)+Active(anon)这两项里的匿名页给交换到磁盘swap out然后再读入到内存swap in后分配的内存。**由于读入到内存后原来的Swap File还在所以SwapCached也可以认为是File-backed page即属于Page Cache。**这样做的目的也是为了减少I/O。你是不是觉得这个过程有些复杂我们用一张图直观地看一下
![](https://static001.geekbang.org/resource/image/a9/7b/a9010acc1d7b55d5c24562d2c32b437b.jpg?wh=4336*1491)
我希望你能通过这个简单的示意图明白SwapCached是怎么产生的。在这个过程中你要注意SwapCached只在Swap分区打开的情况下才会有而我建议你在生产环境中关闭Swap分区因为Swap过程产生的I/O会很容易引起性能抖动。
除了SwapCachedPage Cache中的Shmem是指匿名共享映射这种方式分配的内存free命令中shared这一项比如tmpfs临时文件系统这部分在真实的生产环境中产生的问题比较少不是我们今天的重点内容我们这节课不对它做过多关注你知道有这回事就可以了。
当然了很多同学也喜欢用free命令来查看系统中有多少Page Cache会根据buff/cache来判断存在多少Page Cache。如果你对free命令有所了解的话肯定知道free命令也是通过解析/proc/meminfo得出这些统计数据的这些都可以通过free工具的源码来找到。free命令的源码是开源你可以去看下[procfs](https://gitlab.com/procps-ng/procps)里的free.c文件源码是最直接的理解方式它会加深你对free命令的理解。
不过你是否好奇过free命令中的buff/cache究竟是指什么呢我们在这里先简单地看一下
```
$ free -k
total used free shared buff/cache available
Mem: 7926580 7277960 492392 10000 156228 430680
Swap: 8224764 380748 7844016
```
通过procfs源码里面的[proc/sysinfo.c](https://gitlab.com/procps-ng/procps/-/blob/master/proc/sysinfo.c)这个文件你可以发现buff/cache包括下面这几项
> buff/cache = Buffers + Cached + SReclaimable
通过前面的数据我们也可以验证这个公式: 1224 + 111472 + 43532的和是156228。
另外,这里你要注意,你在做比较的过程中,一定要考虑到这些数据是动态变化的,而且执行命令本身也会带来内存开销,所以这个等式未必会严格相等,不过你不必怀疑它的正确性。
从这个公式中你能看到free命令中的buff/cache是由Buffers、Cached和SReclaimable这三项组成的它强调的是内存的可回收性也就是说可以被回收的内存会统计在这一项。
其中SReclaimable是指可以被回收的内核内存包括dentry和inode等。而这部分内容是内核非常细节性的东西对于应用开发者和运维人员理解起来相对有些难度所以我们在这里不多说。
掌握了Page Cache具体由哪些部分构成之后在它引发一些问题时你就能够知道需要去观察什么。比如说应用本身消耗内存RSS不多的情况下整个系统的内存使用率还是很高那不妨去排查下是不是Shmem(共享内存)消耗了太多内存导致的。
讲到这儿我想你应该对Page Cache有了一些直观的认识了吧当然了有的人可能会说内核的Page Cache这么复杂我不要不可以么
我相信有这样想法的人不在少数如果不用内核管理的Page Cache那有两种思路来进行处理
* 第一种应用程序维护自己的Cache做更加细粒度的控制比如MySQL就是这样做的你可以参考[MySQL Buffer Pool](https://dev.mysql.com/doc/refman/8.0/en/innodb-buffer-pool.html) 它的实现复杂度还是很高的。对于大多数应用而言实现自己的Cache成本还是挺高的不如内核的Page Cache来得简单高效。
* 第二种直接使用Direct I/O来绕过Page Cache不使用Cache了省的去管它了。这种方法可行么那我们继续用数据说话看看这种做法的问题在哪儿
## 为什么需要Page Cache
通过第一张图你其实已经可以直观地看到标准I/O和内存映射会先把数据写入到Page Cache这样做会通过减少I/O次数来提升读写效率。我们看一个具体的例子。首先我们来生成一个1G大小的新文件然后把Page Cache清空确保文件内容不在内存中以此来比较第一次读文件和第二次读文件耗时的差异。具体的流程如下。
先生成一个1G的文件
> $ dd if=/dev/zero of=/home/yafang/test/dd.out bs=4096 count=$((1024\*256))
其次清空Page Cache需要先执行一下sync来将脏页第二节课我会解释一下什么是脏页同步到磁盘再去drop cache。
> $ sync && echo 3 > /proc/sys/vm/drop\_caches
第一次读取文件的耗时如下:
```
$ time cat /home/yafang/test/dd.out &> /dev/null
real 0m5.733s
user 0m0.003s
sys 0m0.213s
```
再次读取文件的耗时如下:
```
$ time cat /home/yafang/test/dd.out &> /dev/null
real 0m0.132s
user 0m0.001s
sys 0m0.130s
```
通过这样详细的过程你可以看到第二次读取文件的耗时远小于第一次的耗时这是因为第一次是从磁盘来读取的内容磁盘I/O是比较耗时的而第二次读取的时候由于文件内容已经在第一次读取时被读到内存了所以是直接从内存读取的数据内存相比磁盘速度是快很多的。**这就是Page Cache存在的意义减少I/O提升应用的I/O速度。**
所以如果你不想为了很细致地管理内存而增加应用程序的复杂度那你还是乖乖使用内核管理的Page Cache吧它是ROI(投入产出比)相对较高的一个方案。
你要知道,我们在做方案抉择时找到一个各方面都很完美的方案还是比较难的,大多数情况下都是经过权衡后来选择一个合适的方案。因为,我一直坚信,合适的就是最好的。
而我之所以说Page Cache是合适的而不是说它是最好的那是因为Page Cache的不足之处也是有的这个不足之处主要体现在它对应用程序太过于透明以至于应用程序很难有好方法来控制它。
为什么这么说呢要想知道这个答案你就需要了解Page Cache的产生过程这里卖个关子我在下一讲会跟你讨论。
## 课堂总结
我们这节课主要是讲述了如何很好地理解Page Cache在我看来要想很好的理解它直观的方式就是从数据入手所以我从如何观测Page Cache出发来带你认识什么是Page Cache然后再从它为什么容易产生问题出发带你回顾了它存在的意义我希望通过这样的方式帮你明确这样几个要点
1. Page Cache是属于内核的不属于用户。
2. Page Cache对应用提升I/O效率而言是一个投入产出比较高的方案所以它的存在还是有必要的。
在我看来如何管理好Page Cache最主要的是你要知道如何来观测它以及观测关于它的一些行为有了这些数据做支撑你才能够把它和你的业务更好地结合起来。而且在我看来当你对某一个概念很模糊、搞不清楚它到底是什么时最好的认知方式就是先搞明白如何来观测它然后动手去观测看看它究竟是如何变化的正所谓纸上得来终觉浅绝知此事要躬行
这节课就讲到这里下一节我们使用数据来观察Page Cache的产生和释放这样一来你就能了解Page Cache的整个生命周期从而对于它引发的一些问题能有一个大概的判断。
## 课后作业
最后我给你留一道思考题请你写一个程序来构造出来Page Cache然后观察/proc/meminfo和/proc/vmstat里面的数据是如何变化的 欢迎在留言区分享你的看法。
感谢你的阅读,如果你认为这节课的内容有收获,也欢迎把它分享给你的朋友,我们下一讲见。