Linux PageCache详解与性能调优
前言
Linux系统经常出现空闲内存少,如同内存不够用一样。其实这是Linux系统内存管理的一个优秀特性:Linux尽量充分利用系统内存。比如:将被访问过的硬盘数据加载到内存(buffer/Cache),利用内存高速读写的特性来提供Linux系统高效率的数据访问能力。
Page Cache概述
应用程序读取Linux文件的两种方式:read、mmap。无论哪种,Linux内核都会相应申请一块Page cache用来缓存文件内容。后续再次读取就直接从Page cache获取,不用访问磁盘文件,以此来提升Linux系统性能。

Linux系统的PageCache通过free命令查看,如图:

cache :表示已读取的数据(快取)
buffer:表示要写入的数据 (缓存)
概念上的差别:cache是针对文件的,buffer是针对磁盘块数据的。
Page Cache的意义
实际案例测试:
首先,提前生成一个大小为4G的文件;测试文件的读取耗时;然后清空Page cache,再次测试文件的读取耗时,两者耗时对比。
1. 生成一个大小为4G的文件
[root@felixzh home]# dd if=/dev/zero of=/home/felixzh_dd.out bs=4096 count=1048576 status=progress

2. 测试文件的读取耗时:
[root@felixzh home]# time cat /home/felixzh_dd.out &> /dev/null

3. 清空Page Cache
[root@felixzh home]# sync && echo 3 > /proc/sys/vm/drop_caches
4. 再次测试文件读取文件的耗时:

测试结果表明:第一次耗时远小于第二次;原因是生成文件时,已经缓存在cache。第一次读取的是内存数据。清空cache后,再次读取则是从磁盘上读取。所以Page Cache存在的意义:减小磁盘I/O,提升应用I/O速度。
Page Cache的产生
两种方式:
Buffered I/O(标准I/O)
Memory-Mapped I/O(储存映射IO)

- 存储映射I/O直接将Page映射到用户空间,用户直接读写PageCache。
- 标准I/O首先写用户缓存区(User page),然后再将用户缓存区里的数据拷贝到内核缓存区(Pagecahe Page)。类似的,读操作先将内核缓存区拷贝到用户缓存区,然后从用户缓存区读取数据。
所以说,存储映射I/O要比标准的I/O效率高一些,毕竟少了“用户空间到内核空间互相拷贝”的过程。
下图简单描述标准I/O写操作流程:

Linux系统提供了脏页同步到磁盘的相关参数:
[root@felixzh home]# cat /proc/vmstat | egrep "dirty|writeback"

其中:
nr_dirty #表示系统中脏页数量(单位为Page 4KB)
nr_writeback #表示正在回写到磁盘的脏页数量(单位为Page 4KB)
Page Cache的死亡
上面提到的使用free命令,查看到的buffer/cache,表示“活着”的Page Cache。什么时候回收呢?两种触发方式:后台回收、直接回收。

借助sar工具可以很直观地观察Page cache的回收行为:

pgscank/s:kswapd(后台回收线程)每秒扫面的Page个数
pgscand/s:Application在内存申请过程中每秒直接扫描的Page个数
pgsteal/s:扫面的page中每秒被回收的个数
%vmeff:pgsteal/(pgscank+pgscand),回收效率,越接近100说明系统越安全,越接近0,说明系统内存压力越大
pgpgin/s 表示每秒从磁盘或SWAP置换到内存的字节数(KB)
pgpgout/s: 表示每秒从内存置换到磁盘或SWAP的字节数(KB)
fault/s: 每秒钟系统产生的缺页数,即主缺页与次缺页之和(major + minor)
majflt/s: 每秒钟产生的主缺页数.
pgfree/s: 每秒被放入空闲队列中的页个数
性能调优
Linux提供影响脏页数据回写的相关参数:
[root@felixzh home]# sysctl -a | grep dirty

vm.dirty_background_ratio 触发回刷的脏数据占用内存的百分比
vm.dirty_background_bytes 触发回刷的脏数据量
vm.dirty_bytes 触发同步写的脏数据量
vm.dirty_ratio 触发同步写的脏数据占可用内存的百分比
vm.dirty_expire_centisecs 脏数据超时回刷时间(单位:1/100S)
vm.dirty_writeback_centisecs 回刷进程定时唤醒时间
如果dirty_ratio大于dirty_background_ratio,是不是就不会达到dirty_ratio呢?首先达到dirty_background_ratio条件,会触发flush进程进行异步的回写操作,此时应用进程仍然可以进行写操作,如果应用进程写入量大于flush进程刷出量。那自然就会达到vm.dirty_ratio这个参数所设定的阙值,此时操作系统会转入同步地进行脏页的过程,阻塞应用进程。
配置实例
场景1:尽可能不丢数据
dirty_background_ratio = 5
dirty_ratio = 10
dirty_writeback_centisecs = 50
dirty_expire_centisecs = 100
此配置通过减少Cache,更加频繁唤醒回刷进程的方式,尽可能让数据回刷。
场景2:追求更高性能
dirty_background_ratio = 50
dirty_ratio = 80
dirty_writeback_centisecs = 2000
dirty_expire_centisecs = 12000
此配置通过增大Cache,延迟回刷唤醒时间来尽可能缓存更多数据,进而实现提高性能。
场景3:突然的IO峰值拖慢整体性能
所谓IO峰值:突发的大量数据写入,导致瞬间IO压力飙升,导致瞬间IO性能狂跌。
dirty_background_ratio = 5
dirty_ratio = 80
dirty_writeback_centisecs = 500
dirty_expire_centisecs = 3000
此配置通过增大Cache和更加频繁唤醒回刷的方式,解决IO峰值的问题。通过保证脏数据比例保持在一个比较低的水平,当突然出现峰值,也有足够的Cache来缓存数据。
Linux内核为每一个块设备分配一个内核线程(flusher threads),线程名为“Writeback",执行体为"wb_workfn",通过workqueue机制实现调度。

总结
Page Cache是一项重要的性能改进:读缓存在绝大多数情况下是有益无害的;写缓存,内核过段时间再异步刷新到磁盘,对加速磁盘I/O有很好的效果,但是当数据未写入磁盘发生宕机时,可能造成数据丢失。