innodb与ext4缓存交互分析

innodb与ext4缓存交互分析

  • 一般来说对一个文件的写入操作包括两部分,对数据本身的写入操作,以及对文件属性(metadata元数据)的写入操作(这里的文件属性包括目录,inode等)。
  • O_DIRECT直接IO绕过了page cache/buffer cache以后为什么还需要fsync()了,就是为了把directory cache和inode cache元数据也刷新到存储设备上。而由于内核和文件系统的更新,有些文件系统能够保证保证在O_DIRECT方式下不用fsync()同步元数据也不会导致数据安全性问题,所以InnoDB又提供了O_DIRECT_NO_FSYNC的方式。

一、Innodb相关

1 整体

在这里插入图片描述

从上图中,我们可以看到,数据InnoDB到磁盘需要经过

  1. InnoDB buffer pool, Redo log buffer。这个是InnoDB应用系统本身的缓冲。
  2. page cache /Buffer cache(可通过o_direct绕过)。这个是vfs层的缓冲。
  3. Inode cache/directory buffer。这个也是vfs层的缓冲。需要通过O_SYNC或者fsync()来刷新。
  4. Write-Back buffer。(可设置存储控制器参数绕过)
  5. Disk on-borad buffer。(可通过设置磁盘控制器参数绕过)

这里我们使用术语“缓冲”(一般为buffer)来表示对数据写的暂存,使用术语“缓存”(一般为cache)来表示对数据读的暂存。顾名思义,由于底层存储设备和内存之间速率的差异,缓冲是用来暂“缓”对底层存储设备IO的“冲”击。缓存主要是在内存中暂“存”从磁盘读到的数据,以便接下来对这些数据的访问不用再次访问慢速的底层存储设备。

RDS的几个参数的默认情况

innodb_flush_method = O_DIRECT

innodb_flush_log_at_trx_commit = 1

innodb_flush_log_at_timeout = 1

2 innodb

该层的缓冲都放在主机内存中,它的目的主要是在应用层管理自己的数据,避免慢速的读写操作影响了InnoDB的响应时间。

InnoDB层主要包括两个buffer:redo log buffer和innodb buffer pool。

redo log buffer用来暂存对重做日志redo log的日志写,InnoDB buffer pool存储了从磁盘设备读到的InnoDB数据,也缓冲了对InnoDB数据写,即脏页数据。

如果主机掉电或者MySQL异常宕机,innodb buffer pool将无法及时刷新到磁盘,那么InnoDB就只能从上一个checkpoint使用redo log来前滚,(pg也是redolog)而redo log buffer如果不能及时刷新到磁盘,那么由于redo log中数据的丢失,就算使用redo前滚,用户提交的事务由于没有真正的记录到非易失型的磁盘介质中,就丢失掉了。

控制redo log buffer刷新时机的参数是innodb_flush_log_at_trx_commit,而控制redo log buffer和innodb buffer pool刷新方式的参数为innodb_flush_method。针对这两个参数详细介绍的文章有非常多,我们这里主要从缓冲的角度来解析。

2.1 影响刷日志:innodb_flush_log_at_trx_commit

控制redo log buffer的innodb_flush_log_at_trx_commit目前支持3种不同的参数值0,1,2

在这里插入图片描述

< 5.6.6: 每隔一秒将redo log buffer中的数据刷新到磁盘

= 5.6.6:每隔innodb_flush_log_at_timeout秒将数据刷新到磁盘中去。

NOTES:

  • The default setting of 1 is required for full ACID compliance. Logs are written and flushed to disk at each transaction commit.

    事务提交时日志write加flush

  • With a setting of 0, logs are written and flushed to disk once per second. Transactions for which logs have not been flushed can be lost in a crash.

    事务提交时日志只写log buffer,不write也不flush,一秒flush到磁盘一次

  • With a setting of 2, logs are written after each transaction commit and flushed to disk once per second. Transactions for which logs have not been flushed can be lost in a crash

    日志write但不flush,一秒flush到磁盘一次

2.2 影响刷数据和刷日志:innodb_flush_method

控制innodb buffer pool的innodb_flush_method目前支持4种不同的参数值:

  • fdatasync
  • O_DSYNC
  • O_DIRECT
  • O_DIRECT_NO_FSYNC
  1. innodb_flush_method指定的不仅是“数据文件”的刷新方式,也指定了“日志文件”刷新方式。
  2. 前三个参数值只允许在5.6.6和5.6.6之前的版本中用,从5.6.7开始新增了O_DIRECT_NO_FSYNC。也就是说用O_DIRECT打开文件,但是不用fsync()同步数据。这个由于在较新的Linux内核和部分文件系统中,使用O_DIRECT就可以保证数据安全,不用专门再用fsync()来同步,保证元数据也刷新到非易失型的磁盘介质。例如:XFS就不能用这个参数。O_DIRECT绕过了page cache,为什么还要用fsync()再刷新以下,我们在下节专门讨论。
  3. 除了O_DIRECT_NO_FSYNC以外,InnoDB都使用fsync()刷新“数据文件”。这里的异常就是O_DIRECT_NO_FSYNC。
  4. 如果指定O_DIRECT,O_DIRECT_NO_FSYNC,数据文件是以O_DIRECT打开

汇总

Open log open log flush log Open datafile flush datafile
fdatasync fsync() fsync()
O_DSYNC O_SYNC fsync()
O_DIRECT fsync() O_DIRECT fsync()
O_DIRECT_NO_FSYNC fsync() O_DIRECT
All_O_DIRECT(percona) O_DIRECT fsync() O_DIRECT fsync

在这里插入图片描述

Notes:

  • fsync: InnoDB uses the fsync() system call to flush both the data and log files. fsync is the default setting.

    fsync数据和日志都不绕过OS的缓冲区

  • O_DIRECT: InnoDB uses O_DIRECT (or directio() on Solaris) to open the data files, and uses fsync() to flush both the data and log files. This option is available on some GNU/Linux versions, FreeBSD, and Solaris.

    数据绕过os缓冲,日志不绕

  • O_DIRECT_NO_FSYNC: InnoDB uses O_DIRECT during flushing I/O, but skips the fsync() system call after each write operation.

    数据绕过os缓冲,不sync,日志不绕

什么是fsync和o_direct?

写盘分三个动作:

[1] open 打开文件

[1] write 写文件(和open绑定)

[2] flush flush操作(将文件缓存刷到磁盘上)

  • 例如open:open("test.file",O_WRONLY|O_APPDENT|O_SYNC))

    • O_SYNC:当向文件写入数据的时候,只有当数据写到了磁盘时,写入操作才算完成(write才返回成功),而且对应的数据文件的属性(例如文件长度等)也需要更新完成才算write操作成功(元数据)
    • O_DIRECT:打开文件,则读/写操作都会跳过OScache,直接在device(disk)上读/写。因为没有了OScache,所以会O_DIRECT降低文件的顺序读写的效率。
  • write的动作由open的方式决定写cache还是直接写盘

  • flush把缓存刷到磁盘上,为什么O_DIRECT直接写了也要flush,看下面一节

二、System Cache相关(重要)

1 O_DIRECT原理

我们打开一个文件并写入数据,VFS和文件系统是怎么把数据写到硬件层列,下图展示了关键的数据结构:

在这里插入图片描述

https://www.usenix.org/legacy/event/usenix01/full_papers/kroeger/kroeger_html/node8.html

  • page_cache/buffer cache主要用于缓冲内存结构数据和块设备数据。
  • inode-cache用于缓冲inode
  • directory-cache用于缓冲目录结构数据。

根据文件系统和操作系统的不同,一般来说对一个文件的写入操作包括两部分,对数据本身的写入操作,以及对文件属性(metadata元数据)的写入操作(这里的文件属性包括目录,inode等)。

page cache buffer cache inode cache dictory cache
O_DIRECT write bypass write bypass write & no flush write & no flush
O_DSYNC/fdatasync() write & flush write & flush write & no flush write & no flush
O_SYNC/fsync() write & flush write & flush write & flush write & flush
  • O_DSYNC和fdatasync()的区别在于:是在每一个IO提交的时刻都针对对应的page cache和buffer cache进行刷新;还是在一定数据的写操作以后调用fdatasync()的时刻对整个page cache和buffer cache进行刷新。O_SYNC和fsync()的区别同理。
  • page cache和buffer cache的主要区别在于一个是面向实际文件数据,一个是面向块设备。在VFS上层使用open()方式打开那些使用mkfs做成文件系统的文件,你就会用到page cache和buffer cache,而如果你在Linux操作系统上使用dd这种方式来操作Linux的块设备,你就只会用到buffer cache。
  • O_DSYNC和O_SYNC的区别在于:O_DSYNC告诉内核,当向文件写入数据的时候,只有当数据写到了磁盘时,写入操作才算完成(write才返回成功)。O_SYNC比O_DSYNC更严格,不仅要求数据已经写到了磁盘,而且对应的数据文件的属性(例如文件inode,相关的目录变化等)也需要更新完成才算write操作成功。可见O_SYNC较之O_DSYNC要多做一些操作。
  • Open()的referense中还有一个O_ASYNC,它主要用于terminals, pseudoterminals, sockets, 和pipes/FIFOs,是信号驱动的IO,当设备可读写时发送一个信号(SIGIO),应用进程捕获这个信号来进行IO操作。
  • O_SYNC和O_DIRECT都是同步写,也就是说只有写成功了才会返回。

回过头来,我们再来看innodb_flush_log_at_trx_commit的配置就比较好理解了。

O_DIRECT直接IO绕过了page cache/buffer cache以后为什么还需要fsync()了,就是为了把directory cache和inode cache元数据也刷新到存储设备上。

而由于内核和文件系统的更新,有些文件系统能够保证保证在O_DIRECT方式下不用fsync()同步元数据也不会导致数据安全性问题,所以InnoDB又提供了O_DIRECT_NO_FSYNC的方式。

2 O_DIRECT优劣势

在大部分的innodb_flush_method参数值的推荐中都会建议使用O_DIRECT,甚至在percona server分支中还提供了ALL_O_DIRECT,对日志文件也使用了O_DIRECT方式打开。

优势

  • 节省操作系统内存:O_DIRECT直接绕过page cache/buffer cache,这样避免InnoDB在读写数据少占用操作系统的内存,把更多的内存留个innodb buffer pool来使用。
  • 节省CPU:内存到存储设备的传输方式主要有poll,中断和DMA方式。使用O_DIRECT方式提示操作系统尽量使用DMA方式来进行存储设备操作,节省CPU。

劣势

  • 字节对齐:O_DIRECT方式要求写数据时,内存是字节对齐的(对齐的方式根据内核和文件系统的不同而不同)。这就要求数据在写的时候需要有额外的对齐操作。可以通过/sys/block/sda/queue/logical_block_size知道对齐的大小,一般都是512个字节。
  • 无法进行IO合并:O_DIRECT绕过page cache/buffer cache直接写存储设备,这样如果对同一块数据进行重复写就无法在内存中命中,page cache/buffer cache合并写的功能就无法生效了。
  • 降低顺序读写效率:如果使用O_DIRECT打开文件,则读/写操作都会跳过cache,直接在存储设备上读/写。因为没有了cache,所以文件的顺序读写使用O_DIRECT这种小IO请求的方式效率是比较低的。(日志是顺序写的,所以日志也用的话效率会比较低)总的来说,使用O_DIRECT来设置innodb_flush_method并不是100%对所有应用和场景都是适用的。

3 ext4挂盘参数优化

  • noatime

    读操作的atime时间值不会记录到inode中,能减轻读操作对磁盘的频繁写入

  • nodiratime

    此选项只针对目录禁止进行atime更新,这样就可以使ls这样的命令不会更新目录的atime值

  • nodelalloc

    禁用延迟分配,块号会在page cache的时候分配,提前分配好block是ext4的特性,大文件可加快速度

  • nobarrier

    其实简单说barrier是保证日志文件系统的WAL(write ahead logging)一种手段:现代日志文件系统如ext4有个journal区,类似数据库领域的redo log,用于意外崩溃后的快速恢复。数据写入磁盘时理应先写入journal区,再写入数据在磁盘的实际对应位置。磁盘厂商为了加快磁盘写入速度磁盘都内置cache,数据一般都先写入磁盘的cache.。cache能加快写入速度,当然是极好的东西,但磁盘一般会对cache内缓存数据排序使之最优刷新到磁盘,这样就可能导致要刷新的实际数据和journal顺序错乱。一旦系统崩溃,下次开机时磁盘要参考journal区来恢复,但此时journal记录顺序与数据实际刷新顺序不同就会导致数据反而"恢复"到不一致。而barrier如其名“栅栏”,先加一个”栅栏“,保证journal总是先写入记录,然后对应数据才刷新到磁盘,这种方式保证了系统崩溃后磁盘恢复的正确性,但对写入性能有蛮大影响。

  • data=ordered

    ext4 支持根据用户需求采用多种模式的日志记录。ext4 支持 Writeback 模式,它仅记录元数据;或 Ordered 模式,它记录元数据,但写为元数据的数据是从日志中写入的;或 Journal 模式(最可靠的模式),它同时记录元数据和数据。注意,虽然 Journal 模式是确保文件系统一致的最佳选择,但它也是最慢的,因为所有数据都要经过日志。

https://www.kernel.org/doc/Documentation/filesystems/ext4.txt

data= journal 
      All data are committed into the journal prior to being
			written into the main file system.  Enabling this mode will 
			disable delayed allocation and O_DIRECT support.

data= ordered	(*)
      All data are forced directly out to the main file
		  system prior to its metadata being committed to the journal.

data= writeback 
      Data ordering is not preserved, data may be written
			into the main file system after its metadata has been
			committed to the journal.

三、存储器相关

该层的缓冲都放在存储控制器的对应板载cache中,它的目的主要是在存储控制器层缓冲数据,避免慢速块设备读写操作影响了IO的响应时间。

当数据被fsync()等刷到存储层时,首先会发送到存储控制器层。常见的存储控制器就是Raid卡,而目前大部分的Raid卡都有1G或者更大的存储容量。这个缓冲一般为易失性的存储,通过板载电池/电容来保证该“易失性的存储”的数据在机器断电以后仍然会同步到底层的磁盘存储介质上。

关于存储控制器我们有一些几个方面需要注意的:

  1. write back/write through:针对是否使用缓冲,一般的存储控制器都提供write back和write through两种方式。write back方式下,操作系统提交的写数据请求直接写入到缓冲中就返回成功;write through方式下,操作系统提交的写数据请求必须要真正写到底层磁盘介质上才返回成功。
  2. 电池/电容区别:为了保证机器掉电以后在“易失性”缓冲中的数据能够及时刷新到底层磁盘介质上,存储控制器上都有电池/电容来保证。普通的电池有容量衰减的问题,也就是说每隔一段时间,板载的电池都要被控制充放电一次,以保证电池的容量。在电池充放过程中,被设置为write-back的存储控制器会自动变为write through。这个充放电的周期(Learn Cycle周期)一般为90天,LSI卡可以通过MegaCli来查看,如果你每隔一段时间发现IO请求响应时间突然慢下来了,就有可能是这个问题哦。通过MegaCli -AdpEventLog -GetEvents -f mr_AdpEventLog.txt -aALL的日志中的Event Description: Battery started charging就可以确定是否发生了发生了充放电的情况。
  3. read/write ratio:HP的smart array提供对cache的读和写的区别(Accelerator Ratio),hpacucli ctrl all show config detail|grep ‘Accelerator RatioAccelerator Ratio: 25% Read / 75% Write这样你就可以根据应用的实际情况来设置用于缓存读和缓冲写的cache的比例了。
  4. 开启Direct IO:为了能够让上层的设备使用Direct IO方式来绕过raid卡,对Raid需要设置开启DirectIO方式:/opt/MegaRAID/MegaCli/MegaCli64 -LDSetProp -Direct -Immediate -Lall -aAll
  5. LSI flash raid:上面我们提到了“易失性”缓冲,如果我们现在有一个非易失性的缓冲,并且容量达到几百G,这样的存储控制器缓冲是不是更能给底层设备提速?作为老牌的Raid卡厂商,LSI目前就有这样的存储控制器,使用write back方式和比较依赖存储控制器缓冲的应用可以考虑使用这种类型的存储控制器。
  6. write barriers:目前raid卡的cache是否有电池或者电容保护对Linux来说是不可见的,所以Linux为了保证日志文件系统的一致性,默认会打开write barriers,也就是说,它会不断的刷新“易失性”缓冲,这样会大大降低IO性能。所以如果你确信底层的电池能够保证“易失性”缓冲会刷到底层磁盘设备的话,你可以在磁盘mount的时候加上-o nobarrier。

四、磁盘控制器相关

该层的缓冲都放在磁盘控制器的对应板载cache中。存储设备固件(firmware)会按规则排序将写操作真正同步到介质中去。这里主要是保证写的顺序性,对机械磁盘来说,这样可以尽量让一次磁头的移动能够完成更多的磁碟写入操作。

一般来说,DMA控制器也是放在磁盘这一层的,通过DMA控制器直接进行内存访问,能够节省CPU的资源。

对于机械硬盘,因为一般的磁盘设备上并没有电池电容等,无法保证在机器掉电时磁盘cache里面的所有数据能够及时同步到介质上,所以我们强烈建议把disk cache关闭掉。

参考

从InnoDB到最终的介质,我们经过了各种缓冲,他们的目的其实很明确,就是为了解决:内存和磁盘的速度不匹配的问题,或者说是磁盘的速度过慢的问题。

另外,其实最懂数据是否应该缓冲/缓存的还是应用本身,VFS,存储控制器和磁盘只能通过延迟写入(以便合并重复IO,使随机写变成顺序写)来缓解底层存储设备慢速造成的响应速度慢的问题。所以数据库类型的应用都会来自己管理缓冲,然后尽量避免操作系统和底层设备的缓冲。

但是其实由于目前SSD固态硬盘和PCIe Flash卡的出现,内存和磁盘之间的速度差异被大大缩减了,这些缓冲是否必要,软硬件哪些可改进的,对软硬件工程师的一大挑战。

https://www.ibm.com/developerworks/cn/linux/l-anatomy-ext4/index.html

https://www.cnblogs.com/DataArt/p/10229913.html

http://en.wikipedia.org/wiki/Disk_buffer

https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/6/html/Storage_Administration_Guide/writebarrieronoff.html

http://en.wikipedia.org/wiki/Direct_memory_access

发布了27 篇原创文章 · 获赞 2 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/jackgo73/article/details/105204944
今日推荐