MySQL PageCache 优化管理

叙述

监控线上实例时,曾出现可用内存不足,性能发生抖动的情况。研究后发现是日志文件的page cache占用了大量的内存(200G+),导致系统可立即分配的内存不足,影响了系统性能。

查看linux内核文档发现,操作系统在内存的使用未超过上限时,不会主动释放page cache,以求达到最高的文件访问效率;当遇到较大的内存需求,操作系统会当场淘汰一些page cache以满足需求。由于page cache的释放较为费时,新的进程不能及时得到内存资源,发生了阻塞。

据此,考虑能否设计一个优化,在page cache占据大量内存前,使用linux内核中提供的posix_fadvise等缓存管理方法,由Mysql主动释放掉无用的page cache,来缓解内存压力。本文先介绍文件的page cache机制,并介绍应用程序级的管理方法,最后介绍针对Mysql日志文件的内存优化。


Page Cache机制

页面缓存(Page Cache)是Linux内核中针对文件I/O的一项优化,Linux从内存中划出了一块区域来缓存文件页,如果要访问外部磁盘上的文件页,首先将这些页面拷贝到内存中,再进行读写。由于硬件结构限制,磁盘的I/O速度比内存慢很多,因此使用Page cache能够大大加速文件的读写速度。

Page Cache的机制如上图所示,具体来说,当应用程序读文件时,系统先检查读取的文件页是否在缓存中;如果在,直接读出即可;如果不在,就将其从磁盘中读入缓存,再读出。此时如果内存有足够的内存空间,该页可以在page cache中驻留,其他进程再访问该部分数据时,就不需要访问磁盘了。

同样,在写文件之前,系统先检查对应的页是否已经在缓存中;如果在,就直接将数据写入page cache,使其成为脏页(drity page)等待刷盘;如果不在,就在缓存中新增一个页面并写入数据(这一页面也是脏页)。真正的磁盘I/O会由操作系统调用fsync等方法来实现,这一调用可以是异步的,保证磁盘I/O不影响文件读写的效率。 在Mysql中,我们说的写文件(write)通常是指将数据写入page cache中,而刷盘或落盘(fsync)才真正将数据写入磁盘中的文件。

程序将数据写入page cache后,可以主动进行刷脏(如调用fsync),也可以放手不管,等待内核帮忙刷脏。在linux内核中,有关自动刷脏的参数如下。

dirty_background_ratio
// 触发文件系统异步刷脏的脏页占总可用内存的最高百分比,当脏页占总可用内存的比例超过该值,后台回写进程被触发进行异步刷脏。

dirty_ratio
// 触发文件系统同步刷脏的脏页占总可用内存的最高百分比,当脏页占总可用内存的比例超过该值,生成新的写文件操作的进程会先执行刷脏。

dirty_background_bytes & dirty_bytes
// 上述两种刷脏条件还可通过设置最高字节数而非比例触发。如果设置bytes版本,则ratio版本将变为0,反之亦然。

dirty_expire_centisecs
// 这个参数指定了脏页多长时间后会被周期性刷脏。下次周期性刷脏时,脏页存活时间超过该值的页面都将被刷入磁盘。

dirty_writeback_centisecs 
// 这个参数指定了多长时间唤醒一次刷脏进程,检查缓存并刷下所有可以刷脏的页面。该参数设为零内核会暂停周期性刷脏。

Page Cache默认由系统调度分配,当free的内存高于内核的低水位线(watermark[WMARK_MIN])时,系统会尽量让用户充分使用缓存,因为它认为这样内存的利用效率最高;当低于低水位线时,就按照LRU的顺序回收page cache。正是这种策略,使得内存的free的部分越来越小,cache的部分越来越大,造成了文章开头提到的问题。

实际上,Mysql中许多文件有着固定的访问模式,它们的页面不会被短时间内多次访问,例如redo log和binlog文件。在实例正常运行的状态下,Redo log只是持久化每次操作的物理日志,写入文件后就没有读操作;binlog文件在写入后,也只会被dump线程所访问。


Mysql日志优化策略

Mysql中不同文件有着不同的访问行为,日志文件是一种顺序读写占绝大多数的文件,因此我们可以为binlog和redo log设计相应的管理策略,来清除暂时不会使用的page cache。

Page cache是系统资源,不属于某个进程管理,因此无法通过进程内存使用的情况来观察优化效果。我们可以使用vmtouch工具来查看page cache的使用情况。

Redo log

在Innodb层,Redo log的主要职责是数据持久化,实现先写日志再写数据的WAL机制。Redo log文件大小和个数固定,由innodb_log_file_size和innodb_log_files_in_group参数控制,这些文件连在一起,被Innodb当成一个整体循环使用。

Redo log写page cache和刷盘分别由线程log_writer和log_flusher异步执行,8.0版本中还实现了写log buffer的无锁化,其具体可参见往期月报:MySQL · 引擎特性 · The design of mysql8.0 redolog

在正常运行的实例中,redo log只有写操作。在写入某个文件的某页后,需要较长的一段时间log_flusher才会再次推进到该页,因此无需保留page cache。

Redo Log的刷盘由log_flusher线程异步执行,因此可以将page cache的释放操作放在log_flusher线程上,每次flush刷脏后执行。这样每次需要释放的page cache较少,耗时较短;

Binlog

在mysql实例正常运行过程中,binlog主要用来做主从复制。Binlog文件的大小由参数max_binlog_size指定,数量没有限制。Binlog的刷盘由参数sync_binlog控制,当sync_binlog为0的时候,刷盘由操作系统负责,异步执行;当不为0的时候,其数值为定期sync磁盘的binlog commit group数,由主线程同步执行刷盘。

没有从库挂载时,binlog只有写操作,保留page cache意义不大。sync_binlog大于0时,刷盘操作以事务为单位,在主线程中拿LOCK_log锁同步执行,如果在每次刷盘后进行fadvise,会阻塞较多的主线程。

因此,将page cache的释放延后到rotate执行,即在关闭旧文件并且成功开启新文件,放掉LOCK_log锁后,释放旧文件的page cache。这样,fadvise操作只会阻塞负责rotate的线程,不会影响到其他线程(因为其他线程都在新的binlog文件中操作)。Rotate执行过程中会调用sync方法刷脏,因此在rotate后释放page cache无需提前刷脏。

有从库挂载时,每次binlog刷盘后,会有dump线程来读取binlog文件的更新,并将更新内容发送到从库。当binlog文件的最后写入位置与dump线程的读取位置比较近(如相距3个文件以内)时,在dump线程读完binlog后再释放page cache效率较高,因为dump可以从page cache中读到更新内容,无需磁盘I/O。这种情况下,将page cache的释放延后到dump线程rotate成功后执行。Dump线程切换binlog时,旧文件已被主线程刷脏,而dump线程只会做读操作,因此不会产生脏页,释放page cache前无需再次刷脏。


总结

系统对page cache的管理,在一些情况下可能有所欠缺,我们可以通过内核提供的posix_fadvise予以干预。在几乎不损失性能的前提下,可以通过主动释放Mysql日志文件的page cache的方法,达到减缓内存压力的目的。对于极端内存需求的场景,这一优化能够很好的预防性能抖动的发生。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值