mirror of
https://github.com/cheetahlou/CategoryResourceRepost.git
synced 2026-05-10 19:54:28 +08:00
mod
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
<audio id="audio" title="01 基础篇 | 如何用数据观测Page Cache?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/f9/84/f98yy95aff1459863b134b975bced684.mp3"></audio>
|
||||
|
||||
你好,我是邵亚方。今天我想和你聊一聊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 Cache,Page Cache的产生和回收是什么样的。这样一来,你会从本质到表象,透彻理解它,深切感受它和你的应用程序之间的关系,从而能更好地理解上面提到的四个问题。
|
||||
|
||||
不过,在这里我想给你提个醒,要学习今天的内容,你最好具备一些Linux编程的基础,比如,如何打开一个文件;如何读写一个文件;如何关闭一个文件等等。这样,你理解今天的内容会更加容易,当然了,不具备也没有关系,如果遇到你实在看不懂的地方,你可以查阅《UNIX环境高级编程》这本书,它是每一位Linux开发者以及运维人员必看的入门书籍。
|
||||
|
||||
好了,话不多说,我们进入今天的学习。
|
||||
|
||||
## 什么是Page Cache?
|
||||
|
||||
我记得很多应用开发者或者运维在向我寻求帮助,解决Page Cache引起的问题时,总是喜欢问我Page Cache到底是属于内核还是属于用户?针对这样的问题,我一般会让他们先看下面这张图:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/f3/1b/f344917f3cacd5bc06ae7c743a217f1b.png" alt="" title="应用程序产生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。你是不是觉得这个过程有些复杂?我们用一张图直观地看一下:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/a9/7b/a9010acc1d7b55d5c24562d2c32b437b.jpg" alt="">
|
||||
|
||||
我希望你能通过这个简单的示意图明白SwapCached是怎么产生的。在这个过程中你要注意,SwapCached只在Swap分区打开的情况下才会有,而我建议你在生产环境中关闭Swap分区,因为Swap过程产生的I/O会很容易引起性能抖动。
|
||||
|
||||
除了SwapCached,Page 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是属于内核的,不属于用户。
|
||||
1. Page Cache对应用提升I/O效率而言是一个投入产出比较高的方案,所以它的存在还是有必要的。
|
||||
|
||||
在我看来,如何管理好Page Cache,最主要的是你要知道如何来观测它以及观测关于它的一些行为,有了这些数据做支撑,你才能够把它和你的业务更好地结合起来。而且,在我看来,当你对某一个概念很模糊、搞不清楚它到底是什么时,最好的认知方式就是先搞明白如何来观测它,然后动手去观测看看它究竟是如何变化的,正所谓纸上得来终觉浅,绝知此事要躬行!
|
||||
|
||||
这节课就讲到这里,下一节我们使用数据来观察Page Cache的产生和释放,这样一来,你就能了解Page Cache的整个生命周期,从而对于它引发的一些问题能有一个大概的判断。
|
||||
|
||||
## 课后作业
|
||||
|
||||
最后我给你留一道思考题,请你写一个程序来构造出来Page Cache,然后观察/proc/meminfo和/proc/vmstat里面的数据是如何变化的, 欢迎在留言区分享你的看法。
|
||||
|
||||
感谢你的阅读,如果你认为这节课的内容有收获,也欢迎把它分享给你的朋友,我们下一讲见。
|
||||
@@ -0,0 +1,200 @@
|
||||
<audio id="audio" title="02 基础篇 | Page Cache是怎样产生和释放的?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/e6/a8/e6f7e83d26b67cace8722a729fa02ea8.mp3"></audio>
|
||||
|
||||
你好,我是邵亚方。
|
||||
|
||||
上一讲,我们主要讲了“什么是Page Cache”(What),“为什么需要Page Cache”(Why),我们这堂课还需要继续了解一下“How”:**也就是Page Cache是如何产生和释放的。**
|
||||
|
||||
在我看来,对Page Cache的“What-Why-How”都有所了解之后,你才会对它引发的问题,比如说Page Cache引起的load飙高问题或者应用程序的RT抖动问题更加了然于胸,从而防范于未然。
|
||||
|
||||
其实,Page Cache是如何产生和释放的,通俗一点的说就是它的“生”(分配)与“死”(释放),即Page Cache的生命周期,那么接下来,我们就先来看一下它是如何“诞生”的。
|
||||
|
||||
## Page Cache是如何“诞生”的?
|
||||
|
||||
Page Cache的产生有两种不同的方式:
|
||||
|
||||
- Buffered I/O(标准I/O);
|
||||
- Memory-Mapped I/O(存储映射I/O)。
|
||||
|
||||
这两种方式分别都是如何产生Page Cache的呢?来看下面这张图:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/4e/52/4eb820e15a5560dee4b847227ee75752.jpg" alt="" title="Page Cache产生方式示意图">
|
||||
|
||||
从图中你可以看到,虽然二者都能产生Page Cache,但是二者的还是有些差异的:
|
||||
|
||||
标准I/O是写的(write(2))用户缓冲区(Userpace Page对应的内存),然后再将用户缓冲区里的数据拷贝到内核缓冲区(Pagecache Page对应的内存);如果是读的(read(2))话则是先从内核缓冲区拷贝到用户缓冲区,再从用户缓冲区读数据,也就是buffer和文件内容不存在任何映射关系。
|
||||
|
||||
对于存储映射I/O而言,则是直接将Pagecache Page给映射到用户地址空间,用户直接读写Pagecache Page中内容。
|
||||
|
||||
显然,存储映射I/O要比标准I/O效率高一些,毕竟少了“用户空间到内核空间互相拷贝”的过程。这也是很多应用开发者发现,为什么使用内存映射I/O比标准I/O方式性能要好一些的主要原因。
|
||||
|
||||
我们来用具体的例子演示一下Page Cache是如何“诞生”的,就以其中的标准I/O为例,因为这是我们最常使用的一种方式,如下是一个简单的示例脚本:
|
||||
|
||||
```
|
||||
#!/bin/sh
|
||||
|
||||
#这是我们用来解析的文件
|
||||
MEM_FILE="/proc/meminfo"
|
||||
|
||||
#这是在该脚本中将要生成的一个新文件
|
||||
NEW_FILE="/home/yafang/dd.write.out"
|
||||
|
||||
#我们用来解析的Page Cache的具体项
|
||||
active=0
|
||||
inactive=0
|
||||
pagecache=0
|
||||
|
||||
IFS=' '
|
||||
|
||||
#从/proc/meminfo中读取File Page Cache的大小
|
||||
function get_filecache_size()
|
||||
{
|
||||
items=0
|
||||
while read line
|
||||
do
|
||||
if [[ "$line" =~ "Active:" ]]; then
|
||||
read -ra ADDR <<<"$line"
|
||||
active=${ADDR[1]}
|
||||
let "items=$items+1"
|
||||
elif [[ "$line" =~ "Inactive:" ]]; then
|
||||
read -ra ADDR <<<"$line"
|
||||
inactive=${ADDR[1]}
|
||||
let "items=$items+1"
|
||||
fi
|
||||
|
||||
|
||||
if [ $items -eq 2 ]; then
|
||||
break;
|
||||
fi
|
||||
done < $MEM_FILE
|
||||
}
|
||||
|
||||
#读取File Page Cache的初始大小
|
||||
get_filecache_size
|
||||
let filecache="$active + $inactive"
|
||||
|
||||
#写一个新文件,该文件的大小为1048576 KB
|
||||
dd if=/dev/zero of=$NEW_FILE bs=1024 count=1048576 &> /dev/null
|
||||
|
||||
#文件写完后,再次读取File Page Cache的大小
|
||||
get_filecache_size
|
||||
|
||||
#两次的差异可以近似为该新文件内容对应的File Page Cache
|
||||
#之所以用近似是因为在运行的过程中也可能会有其他Page Cache产生
|
||||
let size_increased="$active + $inactive - $filecache"
|
||||
|
||||
#输出结果
|
||||
echo "File size 1048576KB, File Cache increased" $size_inc
|
||||
|
||||
```
|
||||
|
||||
在这里我提醒你一下,在运行该脚本前你要确保系统中有足够多的free内存(避免内存紧张产生回收行为),最终的测试结果是这样的:
|
||||
|
||||
>
|
||||
File size 1048576KB, File Cache increased 1048648KB
|
||||
|
||||
|
||||
通过这个脚本你可以看到,在创建一个文件的过程中,代码中/proc/meminfo里的Active(file)和Inactive(file)这两项会随着文件内容的增加而增加,它们增加的大小跟文件大小是一致的(这里之所以略有不同,是因为系统中还有其他程序在运行)。另外,如果你观察得很仔细的话,你会发现增加的Page Cache是Inactive(File)这一项,**你可以去思考一下为什么会这样?**这里就作为咱们这节课的思考题。
|
||||
|
||||
当然,这个过程看似简单,但是它涉及的内核机制还是很多的,换句话说,可能引起问题的地方还是很多的,我们用一张图简单描述下这个过程:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/c7/5e/c728b8a189fb4e35e536cf131c4d9b5e.jpg" alt="">
|
||||
|
||||
这个过程大致可以描述为:首先往用户缓冲区buffer(这是Userspace Page)写入数据,然后buffer中的数据拷贝到内核缓冲区(这是Pagecache Page),如果内核缓冲区中还没有这个Page,就会发生Page Fault会去分配一个Page,拷贝结束后该Pagecache Page是一个Dirty Page(脏页),然后该Dirty Page中的内容会同步到磁盘,同步到磁盘后,该Pagecache Page变为Clean Page并且继续存在系统中。
|
||||
|
||||
我建议你可以将Alloc Page理解为Page Cache的“诞生”,将Dirty Page理解为Page Cache的婴幼儿时期(最容易生病的时期),将Clean Page理解为Page Cache的成年时期(在这个时期就很少会生病了)。
|
||||
|
||||
**但是请注意,并不是所有人都有童年的**,比如孙悟空,一出生就是成人了,Page Cache也一样,如果是读文件产生的Page Cache,它的内容跟磁盘内容是一致的,所以它一开始是Clean Page,除非改写了里面的内容才会变成Dirty Page(返老还童)。
|
||||
|
||||
就像我们为了让婴幼儿健康成长,要悉心照料他/她一样,为了提前发现或者预防婴幼儿时期的Page Cache发病,我们也需要一些手段来观测它:
|
||||
|
||||
```
|
||||
$ cat /proc/vmstat | egrep "dirty|writeback"
|
||||
nr_dirty 40
|
||||
nr_writeback 2
|
||||
|
||||
```
|
||||
|
||||
如上所示,nr_dirty表示当前系统中积压了多少脏页,nr_writeback则表示有多少脏页正在回写到磁盘中,他们两个的单位都是Page(4KB)。
|
||||
|
||||
通常情况下,小朋友们(Dirty Pages)聚集在一起(脏页积压)不会有什么问题,但在非常时期比如流感期间,就很容易导致聚集的小朋友越多病症就会越严重。与此类似,Dirty Pages如果积压得过多,在某些情况下也会容易引发问题,至于是哪些情况,又会出现哪些问题,我们会在案例篇中具体讲解。
|
||||
|
||||
明白了Page Cache的“诞生”后,我们再来看一下Page Cache的“死亡”:它是如何被释放的?
|
||||
|
||||
## Page Cache是如何“死亡”的?
|
||||
|
||||
你可以把Page Cache的回收行为(Page Reclaim)理解为Page Cache的“自然死亡”。
|
||||
|
||||
言归正传,我们知道,服务器运行久了后,系统中free的内存会越来越少,用free命令来查看,大部分都会是used内存或者buff/cache内存,比如说下面这台生产环境中服务器的内存使用情况:
|
||||
|
||||
```
|
||||
$ free -g
|
||||
total used free shared buff/cache available
|
||||
Mem: 125 41 6 0 79 82
|
||||
Swap: 0 0 0
|
||||
|
||||
```
|
||||
|
||||
free命令中的buff/cache中的这些就是“活着”的Page Cache,那它们什么时候会“死亡”(被回收)呢?我们来看一张图:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/1d/bb/1d430c93e397e23d67d12e28827c4bbb.jpg" alt="">
|
||||
|
||||
你可以看到,应用在申请内存的时候,即使没有free内存,只要还有足够可回收的Page Cache,就可以通过回收Page Cache的方式来申请到内存,**回收的方式主要是两种:直接回收和后台回收。**
|
||||
|
||||
那它是具体怎么回收的呢?你要怎么观察呢?其实在我看来,观察Page Cache直接回收和后台回收最简单方便的方式是使用sar:
|
||||
|
||||
```
|
||||
$ sar -B 1
|
||||
02:14:01 PM pgpgin/s pgpgout/s fault/s majflt/s pgfree/s pgscank/s pgscand/s pgsteal/s %vmeff
|
||||
|
||||
|
||||
02:14:01 PM 0.14 841.53 106745.40 0.00 41936.13 0.00 0.00 0.00 0.00
|
||||
02:15:01 PM 5.84 840.97 86713.56 0.00 43612.15 717.81 0.00 717.66 99.98
|
||||
02:16:01 PM 95.02 816.53 100707.84 0.13 46525.81 3557.90 0.00 3556.14 99.95
|
||||
02:17:01 PM 10.56 901.38 122726.31 0.27 54936.13 8791.40 0.00 8790.17 99.99
|
||||
02:18:01 PM 108.14 306.69 96519.75 1.15 67410.50 14315.98 31.48 14319.38 99.80
|
||||
02:19:01 PM 5.97 489.67 88026.03 0.18 48526.07 1061.53 0.00 1061.42 99.99
|
||||
|
||||
```
|
||||
|
||||
借助上面这些指标,你可以更加明确地观察内存回收行为,下面是这些指标的具体含义:
|
||||
|
||||
- pgscank/s : kswapd(后台回收线程)每秒扫描的page个数。
|
||||
- pgscand/s: Application在内存申请过程中每秒直接扫描的page个数。
|
||||
- pgsteal/s: 扫描的page中每秒被回收的个数。
|
||||
- %vmeff: pgsteal/(pgscank+pgscand), 回收效率,越接近100说明系统越安全,越接近0说明系统内存压力越大。
|
||||
|
||||
这几个指标也是通过解析/proc/vmstat里面的数据来得出的,对应关系如下:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/46/aa/4604ec0da3f87ec003317fb3337fa9aa.jpg" alt="">
|
||||
|
||||
关于这几个指标我说一个小插曲,要知道,如果Linux Kernel本身设计不当会给你带来困扰。所以,如果你观察到应用程序的结果跟你的预期并不一致,也有可能是因为内核设计上存在问题,你可以对内核持适当的怀疑态度哦,下面这个是我最近遇到的一个案例。
|
||||
|
||||
如果你对Linus有所了解的话,应该会知道Linus对Linux Kernel设计的第一原则是“never break the user space”。很多内核开发者在设计内核特性的时候,会忽略掉新特性对应用程序的影响,比如在前段时间就有人(Google的一个内核开发者)提交了一个patch来修改内存回收这些指标的含义,但是最终被我和另外一个人(Facebook的一个内核开发者)把他的这个改动给否决掉了。具体的细节并不是咱们这节课的重点,我就不多说了,我建议你在课下看这个讨论:[[PATCH] mm: vmscan: consistent update to pgsteal and pgscan](https://lore.kernel.org/linux-mm/20200507204913.18661-1-shakeelb@google.com/),可以看一下内核开发者们在设计内核特性时是如何思考的,这会有利于你更加全面的去了解整个系统,从而让你的应用程序更好地融入到系统中。
|
||||
|
||||
## 课堂总结
|
||||
|
||||
以上就是本节课的全部内容了,本节课,我们主要讲了Page Caceh是如何“诞生”的,以及如何“死亡”的,我要强调这样几个重点:
|
||||
|
||||
- Page Cache是在应用程序读写文件的过程中产生的,所以在读写文件之前你需要留意是否还有足够的内存来分配Page Cache;
|
||||
- Page Cache中的脏页很容易引起问题,你要重点注意这一块;
|
||||
- 在系统可用内存不足的时候就会回收Page Cache来释放出来内存,我建议你可以通过sar或者/proc/vmstat来观察这个行为从而更好的判断问题是否跟回收有关
|
||||
|
||||
总的来说,Page Cache的生命周期对于应用程序而言是相对比较透明的,即它的分配与回收都是由操作系统来进行管理的。正是因为这种“透明”的特征,所以应用程序才会难以控制Page Cache,Page Cache才会容易引发那么多问题。在接下来的案例篇里,我们就来看看究竟会引发什么样子的问题,以及你正确的分析思路是什么样子的。
|
||||
|
||||
## 课后作业
|
||||
|
||||
因为每个人的关注点都不一样,对问题的理解也不一样。假如你是一个应用开发者,你会更加关注应用的性能和稳定性;假如你是一个运维人员,你会更加关注系统的稳定性;假如你是初学内核的开发者,你会想要关注内核的实现机制。
|
||||
|
||||
所以我留了不同的作业题,主题是围绕“Inactive与Active Page Cache的关系”当然了,对应的难度也不同:
|
||||
|
||||
<li>
|
||||
如果你是一名应用开发者,那么我想问问你为什么第一次读写某个文件,Page Cache是Inactive的?如何让它变成Active的呢?在什么情况下Active的又会变成Inactive的呢?明白了这个问题,你会对应用性能调优有更加深入的理解。
|
||||
</li>
|
||||
<li>
|
||||
如果你是一名运维人员,那么建议你思考一下,系统中有哪些控制项可以影响Inactive与Active Page Cache的大小或者二者的比例?
|
||||
</li>
|
||||
<li>
|
||||
如果你是一名初学内核的开发者,那么我想问你,对于匿名页而言,当产生一个匿名页后它会首先放在Active链表上;而对于文件页而言,当产生一个文件页后它会首先放在Inactive链表上。请问为什么会这样子?这是合理的吗?欢迎在留言区分享你的看法。
|
||||
</li>
|
||||
|
||||
感谢你的阅读,如果你认为这节课的内容有收获,也欢迎把它分享给你的朋友,我们下一讲见。
|
||||
@@ -0,0 +1,188 @@
|
||||
<audio id="audio" title="03 案例篇 | 如何处理Page Cache难以回收产生的load飙高问题?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/06/2b/06fef6388306bdcc63edcf79ca73612b.mp3"></audio>
|
||||
|
||||
你好,我是邵亚方。今天这节课,我想跟你聊一聊怎么处理在生产环境中,因为Page Cache管理不当引起的系统load飙高的问题。
|
||||
|
||||
相信你在平时的工作中,应该会或多或少遇到过这些情形:系统很卡顿,敲命令响应非常慢;应用程序的RT变得很高,或者抖动得很厉害。在发生这些问题时,很有可能也伴随着系统load飙得很高。
|
||||
|
||||
那这是什么原因导致的呢?据我观察,大多是有三种情况:
|
||||
|
||||
- 直接内存回收引起的load飙高;
|
||||
- 系统中脏页积压过多引起的load飙高;
|
||||
- 系统NUMA策略配置不当引起的load飙高。
|
||||
|
||||
这是应用开发者和运维人员向我咨询最多的几种情况。问题看似很简单,但如果对问题产生的原因理解得不深,解决起来就会很棘手,甚至配置得不好,还会带来负面的影响。
|
||||
|
||||
所以这节课,我们一起来分析下这三种情况,可以说,搞清楚了这几种情况,你差不多就能解决掉绝大部分Page Cache引起的load飙高问题了。如果你对问题的原因排查感兴趣,也不要着急,在第5讲,我会带你学习load飙高问题的分析方法。
|
||||
|
||||
接下来,我们就来逐一分析下这几类情况。
|
||||
|
||||
## 直接内存回收引起load飙高或者业务时延抖动
|
||||
|
||||
直接内存回收是指在进程上下文同步进行内存回收,那么它具体是怎么引起load飙高的呢?
|
||||
|
||||
因为直接内存回收是在进程申请内存的过程中同步进行的回收,而这个回收过程可能会消耗很多时间,进而导致进程的后续行为都被迫等待,这样就会造成很长时间的延迟,以及系统的CPU利用率会升高,最终引起load飙高。
|
||||
|
||||
我们详细地描述一下这个过程,为了尽量不涉及太多技术细节,我会用一张图来表示,这样你理解起来会更容易。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/fe/00/fe84eb2bd4956bbbdd5b0259df8c9400.jpg" alt="" title="内存回收过程">
|
||||
|
||||
从图里你可以看到,在开始内存回收后,首先进行后台异步回收(上图中蓝色标记的地方),这不会引起进程的延迟;如果后台异步回收跟不上进程内存申请的速度,就会开始同步阻塞回收,导致延迟(上图中红色和粉色标记的地方,这就是引起load高的地址)。
|
||||
|
||||
那么,针对直接内存回收引起load飙高或者业务RT抖动的问题,一个解决方案就是**及早地触发后台回收来避免应用程序进行直接内存回收,那具体要怎么做呢?**
|
||||
|
||||
我们先来了解一下后台回收的原理,如图:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/44/72/44d471fdae7376eb13e6e6bfc70b3172.jpg" alt="">
|
||||
|
||||
它的意思是:当内存水位低于watermark low时,就会唤醒kswapd进行后台回收,然后kswapd会一直回收到watermark high。
|
||||
|
||||
那么,我们可以增大min_free_kbytes这个配置选项来及早地触发后台回收,该选项最终控制的是内存回收水位,不过,内存回收水位是内核里面非常细节性的知识点,我们可以先不去讨论。
|
||||
|
||||
>
|
||||
vm.min_free_kbytes = 4194304
|
||||
|
||||
|
||||
对于大于等于128G的系统而言,将min_free_kbytes设置为4G比较合理,这是我们在处理很多这种问题时总结出来的一个经验值,既不造成较多的内存浪费,又能避免掉绝大多数的直接内存回收。
|
||||
|
||||
该值的设置和总的物理内存并没有一个严格对应的关系,我们在前面也说过,如果配置不当会引起一些副作用,所以在调整该值之前,我的建议是:你可以渐进式地增大该值,比如先调整为1G,观察sar -B中pgscand是否还有不为0的情况;如果存在不为0的情况,继续增加到2G,再次观察是否还有不为0的情况来决定是否增大,以此类推。
|
||||
|
||||
在这里你需要注意的是,即使将该值增加得很大,还是可能存在pgscand不为0的情况(这个略复杂,涉及到内存碎片和连续内存申请,我们在此先不展开,你知道有这么回事儿就可以了)。那么这个时候你要考虑的是,业务是否可以容忍,如果可以容忍那就没有必要继续增加了,也就是说,增大该值并不是完全避免直接内存回收,而是尽量将直接内存回收行为控制在业务可以容忍的范围内。
|
||||
|
||||
这个方法可以用在3.10.0以后的内核上(对应的操作系统为CentOS-7以及之后更新的操作系统)。
|
||||
|
||||
当然了,这样做也有一些缺陷:提高了内存水位后,应用程序可以直接使用的内存量就会减少,这在一定程度上浪费了内存。所以在调整这一项之前,你需要先思考一下,**应用程序更加关注什么,如果关注延迟那就适当地增大该值,如果关注内存的使用量那就适当地调小该值。**
|
||||
|
||||
除此之外,对CentOS-6(对应于2.6.32内核版本)而言,还有另外一种解决方案:
|
||||
|
||||
>
|
||||
vm.extra_free_kbytes = 4194304
|
||||
|
||||
|
||||
那就是将extra_free_kbytes 配置为4G。extra_free_kbytes在3.10以及以后的内核上都被废弃掉了,不过由于在生产环境中还存在大量的机器运行着较老版本内核,你使用到的也可能会是较老版本的内核,所以在这里还是有必要提一下。它的大致原理如下所示:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/7d/d2/7d9e537e23489cd4f5f34fedcd6f89d2.jpg" alt="">
|
||||
|
||||
extra_free_kbytes的目的是为了解决min_free_kbyte造成的内存浪费,但是这种做法并没有被内核主线接收,因为这种行为很难维护会带来一些麻烦,感兴趣的可以看一下这个讨论:[add extra free kbytes tunable](https://lkml.org/lkml/2013/2/17/210)
|
||||
|
||||
总的来说,通过调整内存水位,在一定程度上保障了应用的内存申请,但是同时也带来了一定的内存浪费,因为系统始终要保障有这么多的free内存,这就压缩了Page Cache的空间。调整的效果你可以通过/proc/zoneinfo来观察:
|
||||
|
||||
```
|
||||
$ egrep "min|low|high" /proc/zoneinfo
|
||||
...
|
||||
min 7019
|
||||
low 8773
|
||||
high 10527
|
||||
...
|
||||
|
||||
```
|
||||
|
||||
其中min、low、high分别对应上图中的三个内存水位。你可以观察一下调整前后min、low、high的变化。需要提醒你的是,内存水位是针对每个内存zone进行设置的,所以/proc/zoneinfo里面会有很多zone以及它们的内存水位,你可以不用去关注这些细节。
|
||||
|
||||
## 系统中脏页过多引起load飙高
|
||||
|
||||
接下来,我们分析下由于系统脏页过多引起load飙高的情况。在前一个案例中我们也提到,直接回收过程中,如果存在较多脏页就可能涉及在回收过程中进行回写,这可能会造成非常大的延迟,而且因为这个过程本身是阻塞式的,所以又可能进一步导致系统中处于D状态的进程数增多,最终的表现就是系统的load值很高。
|
||||
|
||||
我们来看一下这张图,这是一个典型的脏页引起系统load值飙高的问题场景:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/90/75/90c693c95d67cfaf89b86edbd1228d75.jpg" alt="">
|
||||
|
||||
如图所示,如果系统中既有快速I/O设备,又有慢速I/O设备(比如图中的ceph RBD设备,或者其他慢速存储设备比如HDD),直接内存回收过程中遇到了正在往慢速I/O设备回写的page,就可能导致非常大的延迟。
|
||||
|
||||
这里我多说一点。这类问题其实是不太好去追踪的,为了更好追踪这种慢速I/O设备引起的抖动问题,我也给Linux Kernel提交了一个patch来进行更好的追踪:[mm/page-writeback: introduce tracepoint for wait_on_page_writeback()](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=19343b5bdd16ad4ae6b845ef829f68b683c4dfb5),这种做法是在原来的基础上增加了回写的设备,这样子用户就能更好地将回写和具体设备关联起来,从而判断问题是否是由慢速I/O设备导致的(具体的分析方法我会在后面第5讲分析篇里重点来讲)。
|
||||
|
||||
那如何解决这类问题呢?一个比较省事的解决方案是控制好系统中积压的脏页数据。很多人知道需要控制脏页,但是往往并不清楚如何来控制好这个度,脏页控制的少了可能会影响系统整体的效率,脏页控制的多了还是会触发问题,所以我们接下来看下如何来衡量好这个“度”。
|
||||
|
||||
首先你可以通过sar -r来观察系统中的脏页个数:
|
||||
|
||||
```
|
||||
$ sar -r 1
|
||||
07:30:01 PM kbmemfree kbmemused %memused kbbuffers kbcached kbcommit %commit kbactive kbinact kbdirty
|
||||
09:20:01 PM 5681588 2137312 27.34 0 1807432 193016 2.47 534416 1310876 4
|
||||
09:30:01 PM 5677564 2141336 27.39 0 1807500 204084 2.61 539192 1310884 20
|
||||
09:40:01 PM 5679516 2139384 27.36 0 1807508 196696 2.52 536528 1310888 20
|
||||
09:50:01 PM 5679548 2139352 27.36 0 1807516 196624 2.51 536152 1310892 24
|
||||
|
||||
```
|
||||
|
||||
kbdirty就是系统中的脏页大小,它同样也是对/proc/vmstat中nr_dirty的解析。你可以通过调小如下设置来将系统脏页个数控制在一个合理范围:
|
||||
|
||||
>
|
||||
<p>vm.dirty_background_bytes = 0<br>
|
||||
vm.dirty_background_ratio = 10<br>
|
||||
vm.dirty_bytes = 0<br>
|
||||
vm.dirty_expire_centisecs = 3000<br>
|
||||
vm.dirty_ratio = 20</p>
|
||||
|
||||
|
||||
调整这些配置项有利有弊,调大这些值会导致脏页的积压,但是同时也可能减少了I/O的次数,从而提升单次刷盘的效率;调小这些值可以减少脏页的积压,但是同时也增加了I/O的次数,降低了I/O的效率。
|
||||
|
||||
**至于这些值调整大多少比较合适,也是因系统和业务的不同而异,我的建议也是一边调整一边观察,将这些值调整到业务可以容忍的程度就可以了,即在调整后需要观察业务的服务质量(SLA),要确保SLA在可接受范围内**。调整的效果你可以通过/proc/vmstat来查看:
|
||||
|
||||
```
|
||||
$ grep "nr_dirty_" /proc/vmstat
|
||||
nr_dirty_threshold 366998
|
||||
nr_dirty_background_threshold 183275
|
||||
|
||||
```
|
||||
|
||||
你可以观察一下调整前后这两项的变化。**这里我要给你一个避免踩坑的提示**,解决该方案中的设置项如果设置不妥会触发一个内核Bug,这是我在2017年进行性能调优时发现的一个内核Bug,我给社区提交了一个patch将它fix掉了,具体的commit见[writeback: schedule periodic writeback with sysctl](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=94af584692091347baea4d810b9fc6e0f5483d42) [](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=94af584692091347baea4d810b9fc6e0f5483d42) , commit log清晰地描述了该问题,我建议你有时间看一看。
|
||||
|
||||
## 系统NUMA策略配置不当引起的load飙高
|
||||
|
||||
除了我前面提到的这两种引起系统load飙高或者业务延迟抖动的场景之外,还有另外一种场景也会引起load飙高,那就是系统NUMA策略配置不当引起的load飙高。
|
||||
|
||||
比如说,我们在生产环境上就曾经遇到这样的问题:系统中还有一半左右的free内存,但还是频频触发direct reclaim,导致业务抖动得比较厉害。后来经过排查发现是由于设置了zone_reclaim_mode,这是NUMA策略的一种。
|
||||
|
||||
设置zone_reclaim_mode的目的是为了增加业务的NUMA亲和性,但是在实际生产环境中很少会有对NUMA特别敏感的业务,这也是为什么内核将该配置从默认配置1修改为了默认配置0: [mm: disable zone_reclaim_mode by default](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=4f9b16a64753d0bb607454347036dc997fd03b82) ,配置为0之后,就避免了在其他node有空闲内存时,不去使用这些空闲内存而是去回收当前node的Page Cache,也就是说,通过减少内存回收发生的可能性从而避免它引发的业务延迟。
|
||||
|
||||
那么如何来有效地衡量业务延迟问题是否由zone reclaim引起的呢?它引起的延迟究竟有多大呢?这个衡量和观察方法也是我贡献给Linux Kernel的:[mm/vmscan: add tracepoints for node reclaim](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=132bb8cfc9e081238e7e2fd0c37c8c75ad0d2963) ,大致的思路就是利用linux的tracepoint来做这种量化分析,这是性能开销相对较小的一个方案。
|
||||
|
||||
我们可以通过numactl来查看服务器的NUMA信息,如下是两个node的服务器:
|
||||
|
||||
```
|
||||
$ numactl --hardware
|
||||
available: 2 nodes (0-1)
|
||||
node 0 cpus: 0 1 2 3 4 5 6 7 8 9 10 11 24 25 26 27 28 29 30 31 32 33 34 35
|
||||
node 0 size: 130950 MB
|
||||
node 0 free: 108256 MB
|
||||
node 1 cpus: 12 13 14 15 16 17 18 19 20 21 22 23 36 37 38 39 40 41 42 43 44 45 46 47
|
||||
node 1 size: 131072 MB
|
||||
node 1 free: 122995 MB
|
||||
node distances:
|
||||
node 0 1
|
||||
0: 10 21
|
||||
1: 21 10
|
||||
|
||||
```
|
||||
|
||||
其中CPU0~11,24~35的local node为node 0;而CPU12~23,36~47的local node为node 1。如下图所示:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/80/e2/80e7c19a8f310d5bf30d368cef86bbe2.jpg" alt="">
|
||||
|
||||
推荐将zone_reclaim_mode配置为0。
|
||||
|
||||
>
|
||||
vm.zone_reclaim_mode = 0
|
||||
|
||||
|
||||
因为相比内存回收的危害而言,NUMA带来的性能提升几乎可以忽略,所以配置为0,利远大于弊。
|
||||
|
||||
好了,对于Page Cache管理不当引起的系统load飙高和业务时延抖动问题,我们就分析到这里,希望通过这篇的学习,在下次你遇到直接内存回收引起的load飙高问题时不再束手无策。
|
||||
|
||||
总的来说,这些问题都是Page Cache难以释放而产生的问题,那你是否想过,是不是Page Cache很容易释放就不会产生问题了?这个答案可能会让你有些意料不到:Page Cache容易释放也有容易释放的问题。这到底是怎么回事呢,我们下节课来分析下这方面的案例。
|
||||
|
||||
## 课堂总结
|
||||
|
||||
这节课我们讲的这几个案例都是内存回收过程中引起的load飙高问题。关于内存回收这事,我们可以做一个形象的类比。我们知道,内存是操作系统中很重要的一个资源,它就像我们在生活过程中很重要的一个资源——钱一样,如果你的钱(内存)足够多,那想买什么就可以买什么,而不用担心钱花完(内存用完)后要吃土(引起load飙高)。
|
||||
|
||||
但是现实情况是我们每个人用来双十一购物的钱(内存)总是有限的,在买东西(运行程序)的时候总需要精打细算,一旦预算快超了(内存快不够了),就得把一些不重要的东西(把一些不活跃的内容)从购物车里删除掉(回收掉),好腾出资金(空闲的内存)来买更想买的东西(运行需要运行的程序)。
|
||||
|
||||
我们讲的这几个案例都可以通过调整系统参数/配置来解决,调整系统参数/配置也是应用开发者和运维人员在发生了内核问题时所能做的改动。比如说,直接内存回收引起load飙高时,就去调整内存水位设置;脏页积压引起load飙高时,就需要去调整脏页的水位;NUMA策略配置不当引起load飙高时,就去检查是否需要关闭该策略。同时我们在做这些调整的时候,一定要边调整边观察业务的服务质量,确保SLA是可以接受的。
|
||||
|
||||
如果你想要你的系统更加稳定,你的业务性能更好,你不妨去研究一下系统中的可配置项,看看哪些配置可以帮助你的业务。
|
||||
|
||||
## 课后作业
|
||||
|
||||
这节课我给你布置的作业是针对直接内存回收的,现在你已经知道直接内存回收容易产生问题,是我们需要尽量避免的,那么我的问题是:请你执行一些模拟程序来构造出直接内存回收的场景(小提示: 你可以通过sar -B中的pgscand来判断是否有了直接内存回收)。欢迎在留言区分享你的看法。
|
||||
|
||||
感谢你的阅读,如果你认为这节课的内容有收获,也欢迎把它分享给你的朋友,我们下一讲见。
|
||||
@@ -0,0 +1,207 @@
|
||||
<audio id="audio" title="04 案例篇 | 如何处理Page Cache容易回收引起的业务性能问题?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/5a/a7/5a1097233a4e86558c16e9320190c7a7.mp3"></audio>
|
||||
|
||||
你好,我是邵亚方。我们在前一节课讲了Page Cache难以回收导致的load飙高问题,这类问题是很直观的,相信很多人都遭遇过。这节课,我们则是来讲相反的一些问题,即Page Cache太容易回收而引起的一些问题。
|
||||
|
||||
这类问题因为不直观所以陷阱会很多,应用开发者和运维人员会更容易踩坑,也正因为这类问题不直观,所以他们往往是一而再再而三地中招之后,才搞清楚问题到底是怎么回事。
|
||||
|
||||
我把大家经常遇到的这类问题做个总结,大致可以分为两方面:
|
||||
|
||||
- 误操作而导致Page Cache被回收掉,进而导致业务性能下降明显;
|
||||
- 内核的一些机制导致业务Page Cache被回收,从而引起性能下降。
|
||||
|
||||
如果你的业务对Page Cache比较敏感,比如说你的业务数据对延迟很敏感,或者再具体一点,你的业务指标对TP99(99分位)要求较高,那你对于这类性能问题应该多多少少有所接触。当然,这并不意味着业务对延迟不敏感,你就不需要关注这些问题了,关注这类问题会让你对业务行为理解更深刻。
|
||||
|
||||
言归正传,我们来看下发生在生产环境中的案例。
|
||||
|
||||
## 对Page Cache操作不当产生的业务性能下降
|
||||
|
||||
我们先从一个相对简单的案例说起,一起分析下误操作导致Page Cache被回收掉的情况,它具体是怎样发生的。
|
||||
|
||||
我们知道,对于Page Cache而言,是可以通过drop_cache来清掉的,很多人在看到系统中存在非常多的Page Cache时会习惯使用drop_cache来清理它们,但是这样做是会有一些负面影响的,比如说这些Page Cache被清理掉后可能会引起系统性能下降。为什么?
|
||||
|
||||
其实这和inode有关,那inode是什么意思呢?inode是内存中对磁盘文件的索引,进程在查找或者读取文件时就是通过inode来进行操作的,我们用下面这张图来表示一下这种关系:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/7e/99/7ef7747f71ef236e8cf5f9378d80da99.jpg" alt="">
|
||||
|
||||
如上图所示,进程会通过inode来找到文件的地址空间(address_space),然后结合文件偏移(会转换成page index)来找具体的Page。如果该Page存在,那就说明文件内容已经被读取到了内存;如果该Page不存在那就说明不在内存中,需要到磁盘中去读取。你可以理解为inode是Pagecache Page(页缓存的页)的宿主(host),如果inode不存在了,那么PageCache Page也就不存在了。
|
||||
|
||||
如果你使用过drop_cache来释放inode的话,应该会清楚它有几个控制选项,我们可以通过写入不同的数值来释放不同类型的cache(用户数据Page Cache,内核数据Slab,或者二者都释放),这些选项你可以去看[Kernel Documentation的描述](https://www.kernel.org/doc/Documentation/sysctl/vm.txt)。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/ab/7d/ab748f14be603df45c2570fe8e24707d.jpg" alt="">
|
||||
|
||||
于是这样就引入了一个容易被我们忽略的问题:**当我们执行echo 2来drop slab的时候****,****它也会把Page Cache给drop掉**,很多运维人员都会忽视掉这一点。
|
||||
|
||||
在系统内存紧张的时候,运维人员或者开发人员会想要通过drop_caches的方式来释放一些内存,但是由于他们清楚Page Cache被释放掉会影响业务性能,所以就期望只去drop slab而不去drop pagecache。于是很多人这个时候就运行 echo 2 > /proc/sys/vm/drop_caches,但是结果却出乎了他们的意料:Page Cache也被释放掉了,业务性能产生了明显的下降。
|
||||
|
||||
很多人都遇到过这个场景:系统正在运行着,忽然Page Cache被释放掉了,由于不清楚释放的原因,所以很多人就会怀疑,是不是由其他人/程序执行了drop_caches导致的。那有没有办法来观察这个inode释放引起Page Cache被释放的行为呢?答案是有的。关于这一点,我们在下一节课会讲到。我们先来分析下如何观察是否有人或者有程序执行过drop_caches。
|
||||
|
||||
由于drop_caches是一种内存事件,内核会在/proc/vmstat中来记录这一事件,所以我们可以通过/proc/vmstat来判断是否有执行过drop_caches。
|
||||
|
||||
```
|
||||
$ grep drop /proc/vmstat
|
||||
drop_pagecache 3
|
||||
drop_slab 2
|
||||
|
||||
```
|
||||
|
||||
如上所示,它们分别意味着pagecache被drop了3次(通过echo 1 或者echo 3),slab被drop了2次(通过echo 2或者echo 3)。如果这两个值在问题发生前后没有变化,那就可以排除是有人执行了drop_caches;否则可以认为是因为drop_caches引起的Page Cache被回收。
|
||||
|
||||
针对这类问题,你除了在执行drop cache前三思而后行之外,还有其他的一些根治的解决方案。在讲这些解决方案之前,我们先来看一个更加复杂一点的案例,它们有一些共性,解决方案也类似,只是接下来这个案例涉及的内核机制更加复杂。
|
||||
|
||||
## 内核机制引起Page Cache被回收而产生的业务性能下降
|
||||
|
||||
我们在前面已经提到过,在内存紧张的时候会触发内存回收,内存回收会尝试去回收reclaimable(可以被回收的)内存,这部分内存既包含Page Cache又包含reclaimable kernel memory(比如slab)。我们可以用下图来简单描述这个过程:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/d3/3c/d33a7264358f20fb063cb49fc3f3163c.jpg" alt="">
|
||||
|
||||
我简单来解释一下这个图。Reclaimer是指回收者,它可以是内核线程(包括kswapd)也可以是用户线程。回收的时候,它会依次来扫描pagecache page和slab page中有哪些可以被回收的,如果有的话就会尝试去回收,如果没有的话就跳过。在扫描可回收page的过程中回收者一开始扫描的较少,然后逐渐增加扫描比例直至全部都被扫描完。这就是内存回收的大致过程。
|
||||
|
||||
接下来我所要讲述的案例就发生在“relcaim slab”中,我们从前一个案例已然知道,如果inode被回收的话,那么它对应的Page Cache也都会被回收掉,所以如果业务进程读取的文件对应的inode被回收了,那么该文件所有的Page Cache都会被释放掉,这也是容易引起性能问题的地方。
|
||||
|
||||
那这个行为是否有办法观察?这同样也是可以通过/proc/vmstat来观察的,/proc/vmstat简直无所不能(这也是为什么我会在之前说内核开发者更习惯去观察/proc/vmstat)。
|
||||
|
||||
```
|
||||
$ grep inodesteal /proc/vmstat
|
||||
pginodesteal 114341
|
||||
kswapd_inodesteal 1291853
|
||||
|
||||
```
|
||||
|
||||
这个行为对应的事件是inodesteal,就是上面这两个事件,其中kswapd_inodesteal是指在kswapd回收的过程中,因为回收inode而释放的pagecache page个数;pginodesteal是指kswapd之外其他线程在回收过程中,因为回收inode而释放的pagecache page个数。所以在你发现业务的Page Cache被释放掉后,你可以通过观察来发现是否因为该事件导致的。
|
||||
|
||||
在明白了Page Cache被回收掉是如何发生的,以及知道了该如何观察之后,我们来看下该如何解决这类问题。
|
||||
|
||||
## 如何避免Page Cache被回收而引起的性能问题?
|
||||
|
||||
我们在分析一些问题时,往往都会想这个问题是我的模块有问题呢,还是别人的模块有问题。也就是说,是需要修改我的模块来解决问题还是需要修改其他模块来解决问题。与此类似,避免Page Cache里相对比较重要的数据被回收掉的思路也是有两种:
|
||||
|
||||
- 从应用代码层面来优化;
|
||||
- 从系统层面来调整。
|
||||
|
||||
从应用程序代码层面来解决是相对比较彻底的方案,因为应用更清楚哪些Page Cache是重要的,哪些是不重要的,所以就可以明确地来对读写文件过程中产生的Page Cache区别对待。比如说,对于重要的数据,可以通过mlock(2)来保护它,防止被回收以及被drop;对于不重要的数据(比如日志),那可以通过madvise(2)告诉内核来立即释放这些Page Cache。
|
||||
|
||||
我们来看一个通过mlock(2)来保护重要数据防止被回收或者被drop的例子:
|
||||
|
||||
```
|
||||
#include <sys/mman.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
|
||||
#define FILE_NAME "/home/yafang/test/mmap/data"
|
||||
#define SIZE (1024*1000*1000)
|
||||
|
||||
|
||||
int main()
|
||||
{
|
||||
int fd;
|
||||
char *p;
|
||||
int ret;
|
||||
|
||||
|
||||
fd = open(FILE_NAME, O_CREAT|O_RDWR, S_IRUSR|S_IWUSR);
|
||||
if (fd < 0)
|
||||
return -1;
|
||||
|
||||
|
||||
/* Set size of this file */
|
||||
ret = ftruncate(fd, SIZE);
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
|
||||
|
||||
/* The current offset is 0, so we don't need to reset the offset. */
|
||||
/* lseek(fd, 0, SEEK_CUR); */
|
||||
|
||||
|
||||
/* Mmap virtual memory */
|
||||
p = mmap(0, SIZE, PROT_READ|PROT_WRITE, MAP_FILE|MAP_SHARED, fd, 0);
|
||||
if (!p)
|
||||
return -1;
|
||||
|
||||
|
||||
/* Alloc physical memory */
|
||||
memset(p, 1, SIZE);
|
||||
|
||||
|
||||
/* Lock these memory to prevent from being reclaimed */
|
||||
mlock(p, SIZE);
|
||||
|
||||
|
||||
/* Wait until we kill it specifically */
|
||||
while (1) {
|
||||
sleep(10);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Unmap the memory.
|
||||
* Actually the kernel will unmap it automatically after the
|
||||
* process exits, whatever we call munamp() specifically or not.
|
||||
*/
|
||||
munmap(p, SIZE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
在这个例子中,我们通过mlock(2)来锁住了读FILE_NAME这个文件内容对应的Page Cache。在运行上述程序之后,我们来看下该如何来观察这种行为:确认这些Page Cache是否被保护住了,被保护了多大。这同样可以通过/proc/meminfo来观察:
|
||||
|
||||
```
|
||||
$ egrep "Unevictable|Mlocked" /proc/meminfo
|
||||
Unevictable: 1000000 kB
|
||||
Mlocked: 1000000 kB
|
||||
|
||||
```
|
||||
|
||||
然后你可以发现,drop_caches或者内存回收是回收不了这些内容的,我们的目的也就达到了。
|
||||
|
||||
在有些情况下,对应用程序而言,修改源码是件比较麻烦的事,如果可以不修改源码来达到目的那就最好不过了。Linux内核同样实现了这种不改应用程序的源码而从系统层面调整来保护重要数据的机制,这个机制就是memory cgroup protection。
|
||||
|
||||
它大致的思路是,将需要保护的应用程序使用memory cgroup来保护起来,这样该应用程序读写文件过程中所产生的Page Cache就会被保护起来不被回收或者最后被回收。memory cgroup protection大致的原理如下图所示:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/71/cc/71e7347264a54773d902aa92535b87cc.jpg" alt="">
|
||||
|
||||
如上图所示,memory cgroup提供了几个内存水位控制线memory.{min, low, high, max} 。
|
||||
|
||||
<li>
|
||||
<p>**memory.max**<br>
|
||||
这是指memory cgroup内的进程最多能够分配的内存,如果不设置的话,就默认不做内存大小的限制。</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>**memory.high**<br>
|
||||
如果设置了这一项,当memory cgroup内进程的内存使用量超过了该值后就会立即被回收掉,所以这一项的目的是为了尽快的回收掉不活跃的Page Cache。</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>**memory.low**<br>
|
||||
这一项是用来保护重要数据的,当memory cgroup内进程的内存使用量低于了该值后,在内存紧张触发回收后就会先去回收不属于该memory cgroup的Page Cache,等到其他的Page Cache都被回收掉后再来回收这些Page Cache。</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>**memory.min**<br>
|
||||
这一项同样是用来保护重要数据的,只不过与memoy.low有所不同的是,当memory cgroup内进程的内存使用量低于该值后,即使其他不在该memory cgroup内的Page Cache都被回收完了也不会去回收这些Page Cache,可以理解为这是用来保护最高优先级的数据的。</p>
|
||||
</li>
|
||||
|
||||
那么,**如果你想要保护你的Page Cache不被回收,你就可以考虑将你的业务进程放在一个memory cgroup中,然后设置memory.{min,low} 来进行保护;与之相反,如果你想要尽快释放你的Page Cache,那你可以考虑设置memory.high来及时的释放掉不活跃的Page Cache。**
|
||||
|
||||
更加细节性的一些设置我们就不在这里讨论了,我建议你可以自己动手来设置后观察一下,这样你理解会更深刻。
|
||||
|
||||
## 课堂总结
|
||||
|
||||
我们在前一篇讲到了Page Cache回收困难引起的load飙高问题,这也是很直观的一类问题;在这一篇讲述的则是一类相反的问题,即Page Cache太容易被回收而引起的一些问题,这一类问题是不那么直观的一类问题。
|
||||
|
||||
对于很直观的问题,我们相对比较容易去观察分析,而且由于它们比较容易观察,所以也相对能够得到重视;对于不直观的问题,则不是那么容易观察分析,相对而言它们也容易被忽视。
|
||||
|
||||
外在的特征不明显,并不意味着它产生的影响不严重,就好比皮肤受伤流血了,我们知道需要立即止血这个伤病也能很容易得到控制;如果是内伤,比如心肝脾肺肾有了问题,则容易被忽视,但是这些问题一旦积压久了往往造成很严重的后果。所以对于这类不直观的问题,我们还是需要去重视它,而且尽量做到提前预防,比如说:
|
||||
|
||||
- 如果你的业务对Page Cache所造成的延迟比较敏感,那你最好可以去保护它,比如通过mlock或者memory cgroup来对它们进行保护;
|
||||
- 在你不明白Page Cache是因为什么原因被释放时,你可以通过/proc/vmstat里面的一些指标来观察,找到具体的释放原因然后再对症下药的去做优化。
|
||||
|
||||
## 课后作业
|
||||
|
||||
这节课给你布置的作业是与mlock相关的,请你思考下,进程调用mlock()来保护内存,然后进程没有运行munlock()就退出了,在进程退出后,这部分内存还被保护吗,为什么?欢迎在留言区分享你的看法。
|
||||
|
||||
感谢你的阅读,如果你认为这节课的内容有收获,也欢迎把它分享给你的朋友,我们下一讲见。
|
||||
@@ -0,0 +1,126 @@
|
||||
<audio id="audio" title="05 分析篇 | 如何判断问题是否由Page Cache产生的?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/41/3c/41cf9a8919b116e3e1b327d7b40d973c.mp3"></audio>
|
||||
|
||||
你好,我是邵亚方。
|
||||
|
||||
在前面几节课里,我们讲了Page Cache的一些基础知识,以及如何去处理Page Cache引发的一些问题。这节课我们来讲讲,如何判断问题是不是由Page Cache引起的。
|
||||
|
||||
我们知道,一个问题往往牵扯到操作系统的很多模块,比如说,当系统出现load飙高的问题时,可能是Page Cache引起的;也可能是锁冲突太厉害,物理资源(CPU、内存、磁盘I/O、网络I/O)有争抢导致的;也可能是内核特性设计缺陷导致的,等等。
|
||||
|
||||
如果我们没有判断清楚问题是如何引起的而贸然采取措施,非但无法解决问题,反而会引起其他负面影响,比如说,load飙高本来是Page Cache引起的,如果你没有查清楚原因,而误以为是网络引起的,然后对网络进行限流,看起来把问题解决了,但是系统运行久了还是会出现load飙高,而且限流这种行为还降低了系统负载能力。
|
||||
|
||||
那么当问题发生时,我们如何判断它是不是由Page Cache引起的呢?
|
||||
|
||||
## Linux问题的典型分析手段
|
||||
|
||||
Linux上有一些典型的问题分析手段,从这些基本的分析方法入手,你可以一步步判断出问题根因。这些分析手段,可以简单地归纳为下图:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/ee/c1/ee08329fc5eb7fb8ddff14dba9ebf0c1.jpg" alt="" title="Linux的典型分析手段">
|
||||
|
||||
从这张图中我们可以看到,Linux内核主要是通过/proc和/sys把系统信息导出给用户,当你不清楚问题发生的原因时,你就可以试着去这几个目录下读取一下系统信息,看看哪些指标异常。比如当你不清楚问题是否由Page Cache引起时,你可以试着去把/proc/vmstat里面的信息给读取出来,看看哪些指标单位时间内变化较大。如果pgscan相关指标变化较大,那就可能是Page Cache引起的,因为pgscan代表了Page Cache的内存回收行为,它变化较大往往意味着系统内存压力很紧张。
|
||||
|
||||
/proc和/sys里面的信息可以给我们指出一个问题分析的大致方向,我们可以判断出问题是不是由Page Cache引起的,但是如果想要深入地分析问题,知道Page Cache是如何引起问题的,我们还需要掌握更加专业的分析手段,专业的分析工具有ftrace,ebpf,perf等。
|
||||
|
||||
当然了,这些专业工具的学习成本也相对略高一些,但你不能觉得它难、成本高,就不学了,因为掌握了这些分析工具后,再遇到疑难杂症,你分析起来会更加得心应手。
|
||||
|
||||
为了让你在遇到问题时更加方便地找到合适的分析工具,我借用[Bredan Gregg的一张图](https://www.slideshare.net/brendangregg/velocity-2015-linux-perf-tools/107),并根据自己的经验,把这张图略作了一些改进,帮助你学习该如何使用这些分析工具:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/0c/97/0ccc072485d8ca2b995a6e7b6a75da97.jpg" alt="">
|
||||
|
||||
在这张图里,整体上追踪方式分为了静态追踪(预置了追踪点)和动态追踪(需要借助probe):
|
||||
|
||||
- 如果你想要追踪的东西已经有了预置的追踪点,那你直接使用这些预置追踪点就可以了;
|
||||
- 如果没有预置追踪点,那你就要看看是否可以使用probe(包括kprobe和uprobe)来实现。
|
||||
|
||||
因为分析工具自身也会对业务造成一些影响(Heisenbug),比如说使用strace会阻塞进程的运行,再比如使用systemtap也会有加载编译的开销等,**所以我们在使用这些工具之前也需要去详细了解下这些工具的副作用,以免引起意料之外的问题**。
|
||||
|
||||
比如我多年以前在使用systemtap的guru(专家)模式的时候,因为没有考虑到systemtap进程异常退出后,可能不会卸载systemtap模块从而引发系统panic的问题。
|
||||
|
||||
上面这些就是Linux问题的一些典型分析方法,了解了这些分析方法,你再遇到问题就能知道该选择什么样的工具来去分析。对于Page Cache而言,首先我们可以通过/proc/vmstat来做一个大致判断,然后再结合Page Cache的tracepoint来做更加深入的分析。
|
||||
|
||||
接下来我们一起分析两个具体问题。
|
||||
|
||||
## 系统现在load很高,是由Page Cache引起的吗?
|
||||
|
||||
我相信你肯定会遇到过这种场景:业务一直稳定运行着却忽然出现很大的性能抖动,或者系统一直稳定运行着却忽然出现较高的load值,那怎么去判断这个问题是不是由Page Cache引起的呢?在这里,我根据自己多年的经验,总结了一些分析的步骤。
|
||||
|
||||
分析问题的第一步,就是需要对系统的概括做一个了解,对于Page Cahe相关的问题,我推荐你**使用sar来采集Page Cache的概况**,它是系统默认配置好的工具,使用起来非常简单方便。
|
||||
|
||||
我在课程的第1讲也提到了对sar的一些使用:比如通过sar -B来分析分页信息(Paging statistics), 以及sar -r来分析内存使用情况统计(Memory utilization statistics)等。在这里,我特别推荐你使用sar里面记录的PSI(Pressure-Stall Information)信息来查看Page Cache产生压力情况,尤其是给业务产生的压力,而这些压力最终都会体现在load上。不过该功能需要4.20以上的内核版本才支持,同时sar的版本也要更新到12.3.3版本以上。比如PSI中表示内存压力的如下输出:
|
||||
|
||||
```
|
||||
some avg10=45.49 avg60=10.23 avg300=5.41 total=76464318
|
||||
full avg10=40.87 avg60=9.05 avg300=4.29 total=58141082
|
||||
|
||||
```
|
||||
|
||||
你需要重点关注avg10这一列,它表示最近10s内存的平均压力情况,如果它很大(比如大于40)那load飙高大概率是由于内存压力,尤其是Page Cache的压力引起的。
|
||||
|
||||
明白了概况之后,我们还需要进一步查看究竟是Page Cache的什么行为引起的系统压力。
|
||||
|
||||
因为sar采集的只是一些常用的指标,它并没有覆盖Page Cache的所有行为,比如说内存规整(memory compaction)、业务workingset等这些容易引起load飙高的问题点。在我们想要分析更加具体的原因时,就需要去采集这些指标了。通常在Page Cache出问题时,这些指标中的一个或多个都会有异常,这里我给你列出一些常见指标:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/ed/bb/ed990308aef09a5918e6855362284dbb.jpg" alt="">
|
||||
|
||||
采集完这些指标后,我们就可以分析Page Cache异常是由什么引起的了。比如说,当我们发现,单位时间内compact_fail变化很大时,那往往意味着系统内存碎片很严重,已经很难申请到连续物理内存了,这时你就需要去调整碎片指数或者手动触发内存规整,来减缓因为内存碎片引起的压力了。
|
||||
|
||||
我们在前面的步骤中采集的数据指标,可以帮助我们来定位到问题点究竟是什么,比如下面这些问题点。但是有的时候,我们还需要知道是什么东西在进行连续内存的申请,从而来做更加有针对性的调整,这就需要进行进一步的观察了。我们可以利用内核预置的相关tracepoint来做更加细致的分析。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/f1/f0/f14faca88b5a765690a6c1540517def0.jpg" alt="">
|
||||
|
||||
我们继续以内存规整(memory compaction)为例,来看下如何利用tracepoint来对它进行观察:
|
||||
|
||||
```
|
||||
#首先来使能compcation相关的一些tracepoing
|
||||
$ echo 1 >
|
||||
/sys/kernel/debug/tracing/events/compaction/mm_compaction_begin/enable
|
||||
$ echo 1 >
|
||||
/sys/kernel/debug/tracing/events/compaction/mm_compaction_end/enable
|
||||
|
||||
#然后来读取信息,当compaction事件触发后就会有信息输出
|
||||
$ cat /sys/kernel/debug/tracing/trace_pipe
|
||||
<...>-49355 [037] .... 1578020.975159: mm_compaction_begin:
|
||||
zone_start=0x2080000 migrate_pfn=0x2080000 free_pfn=0x3fe5800
|
||||
zone_end=0x4080000, mode=async
|
||||
<...>-49355 [037] .N.. 1578020.992136: mm_compaction_end:
|
||||
zone_start=0x2080000 migrate_pfn=0x208f420 free_pfn=0x3f4b720
|
||||
zone_end=0x4080000, mode=async status=contended
|
||||
|
||||
```
|
||||
|
||||
从这个例子中的信息里,我们可以看到是49355这个进程触发了compaction,begin和end这两个tracepoint触发的时间戳相减,就可以得到compaction给业务带来的延迟,我们可以计算出这一次的延迟为17ms。
|
||||
|
||||
很多时候由于采集的信息量太大,我们往往需要借助一些自动化分析的工具来分析,这样会很高效。比如我之前写过一个[perf script](https://lore.kernel.org/linux-mm/20191001144524.GB3321@techsingularity.net/T/)来分析直接内存回收对业务造成的延迟。另外你也可以参考Brendan Gregg基于bcc(eBPF)写的[direct reclaim snoop](https://github.com/iovisor/bcc/blob/master/tools/drsnoop.py)来观察进程因为direct reclaim而导致的延迟。
|
||||
|
||||
## 系统load值在昨天飙得很高,是由Page Cache引起的吗?
|
||||
|
||||
上面的问题是实时发生的,对实时问题来说,因为有现场信息可供采集,所以相对好分析一些。但是有时候,我们没有办法及时地去搜集现场信息,比如问题发生在深夜时,我们没有来得及去采集现场信息,这个时候就只能查看历史记录了。
|
||||
|
||||
我们可以根据sar的日志信息来判断当时发生了什么事情。我之前就遇到过类似的问题。
|
||||
|
||||
曾经有一个业务反馈说RT抖动得比较明显,让我帮他们分析一下抖动的原因,我把业务RT抖动的时间和sar -B里的pgscand不为0的时刻相比较后发现,二者在很多时候都是吻合的。于是,我推断业务抖动跟Page Cache回收存在一些关系,然后我让业务方调vm.min_free_kbytes来验证效果,业务方将该值从初始值90112调整为4G后效果立竿见影,就几乎没有抖动了。
|
||||
|
||||
在这里,我想再次强调一遍,调整vm.min_free_kbytes会存在一些风险,如果系统本身内存回收已经很紧张,再去调大它极有可能触发OOM甚至引起系统宕机。所以在调大的时候,一定要先做一些检查,看看此时是否可以调整。
|
||||
|
||||
当然了,如果你的sysstat版本较新并且内核版本较高,那你也可以观察PSI记录的日志信息是否跟业务抖动相吻合。根据sar的这些信息我们可以推断出故障是否跟Page Cache相关。
|
||||
|
||||
既然是通过sar的日志信息来评判,那么对日志信息的丰富度就有一定要求。你需要对常见的一些问题做一些归纳总结,然后把这些常见问题相关联的指标记录在日志中供事后分析,这样可以帮助你更加全面地分析问题,尤其是发生频率较高的一些问题。
|
||||
|
||||
比如,曾经我们的业务经常发生一些业务抖动,在通过我们上述的分析手段分析出来是compation引起的问题后,而且这类问题较多,我们便把/proc/vmstat里compaction相关的指标(我们在上面的表格里有写到具体是哪些指标)记录到我们日志系统中。在业务再次出现抖动后,我们就可以根据日志信息来判断是否跟compaction相关了。
|
||||
|
||||
## 课堂回顾
|
||||
|
||||
好了,这节课我们就讲到这里,我们简单回顾一下。这节课我们讲了Page Cache问题的分析方法论,按照这个方法论我们几乎可以分析清楚Page Cache相关的所有问题,而且也能帮助我们了解业务的内存访问模式,从而帮助我们更好地对业务来做优化。
|
||||
|
||||
当然这套分析方法论不仅仅适用于Page Cache引发的问题,对于系统其他层面引起的问题同样也适用。让我们再次回顾一下这些要点:
|
||||
|
||||
- 在观察Page Cache的行为时,你可以先从最简单易用的分析工具比如sar入手,来得到一个概况,然后再使用更加专业一些的工具比如tracepoint去做更细致的分析。这样你就能分析清楚Page Cache的详细行为,以及它为什么会产生问题;
|
||||
- 对于很多的偶发性的问题,往往需要采集很多的信息才能抓取出来问题现场,这种场景下最好使用perf script来写一些自动化分析的工具来提升效率;
|
||||
- 如果你担心分析工具会对生产环境产生性能影响,你可以把信息采集下来之后进行离线分析,或者使用ebpf来进行自动过滤分析,请注意ebpf需要高版本内核的支持。
|
||||
|
||||
这是我沉淀下来的定位问题的方法。也希望你在遇到问题时不逃避,刨根问底寻找根本原因是什么,相信你一定也会有自己的问题分析方法论,然后在出现问题时能够快速高效地找到原因。
|
||||
|
||||
## 课后作业
|
||||
|
||||
假设现在内存紧张, 有很多进程都在进行直接内存回收,如何统计出来都是哪些进程在进行直接内存回收呢?欢迎在留言区分享你的看法。
|
||||
|
||||
感谢你的阅读,如果你认为这节课的内容有收获,也欢迎把它分享给你的朋友,我们下一讲见。
|
||||
Reference in New Issue
Block a user