之前在研究MySQL的一个参数innodb_flush_method时,就涉及到了fsync/fdatasync这些库函数。接下来就简单的分析一下sync/fsync/fdatasync的区别。
sync():int sync( void )这就是它的原型,A call to this function will not return as long as there is data which has not been written to the device,sync()同步写,没有写到物理设备就不会返回,但是现实中并不是这样的。在kernel的手册上有解释:BUGS部分(linux中用man查看命令的时候不是都有一个BUGS部分么,就是指的那个)According to the standard specification (e.g., POSIX.1-2001), sync() schedules the writes, but may return before the actual writing is done. However, since version 1.3.20 Linux does actually wait. (This still does not guarantee data integrity: modern disks have large caches.)也就是sync()负责将这些写物理设备的请求放入写队列,但是不一定写真正被完成了。
fsync(int fd):The fsync function can be used to make sure all data associated with the open file fildes is written to the device associated with the descriptor。fsync()负责将一个文件描述符(什么是文件描述符,它是unix、类unix系统打开文件的一种方式,应该相当于打开文件的一个句柄一样)打开的文件写到物理设备,而且是真正的同步写,没有写完成就不会返回,而且写的时候讲文件本身的一些元数据都会更新到物理设备上去,比如atime,mtime等等。
fdatasync(int fd):When a call to the fdatasync function returns, it is ensured that all of the file data is written to the device。它只保证开打文件的数据全部被写到物理设备上,但是一些元数据并不是一定的,这也是它与fsync的区别。
这三个库函数都简单的介绍完,那么为什么需要它们三个呢?最简单的说是从应用的需求来考虑的,sync是全局的,对整个系统都flush,fsync值针对单个文件,fdatasync当初设计是考虑到有特殊的时候一些基本的元数据比如atime,mtime这些不会对以后读取造成不一致性,因此少了这些元数据的同步可能会在性能上有提升(但fsync和fdatasync两者的性能差别有多大?这个不知道有谁测过没)。所以说三者是根据不同的需求而定的。
接下来谈谈flush dirty page,也就是前面说的同步写(没写完的话阻塞后面,直到写完才返回)。为什么是刷脏页?脏页表示缓存中的页(一般也就是内存中)也物理设备上的页处于不一致,不一致是由于在内存中被修改了。所以为了持久化这种内存中的修改,需要flush到物理设备上。根据我的理解,一般来说缓存分成这几种:1>应用程序自己带了缓存,比如InnoDB的buffer pool;2>os层面上的缓存 ;3>磁盘设备自己的缓存,比如raid卡一般都管理着自己的缓存;4>磁盘本身或许会有一点点缓存(这个不确定,自己猜想的,这个即使有估计也是极小的)。好了,那么大部分的时候我们说的flush dirty page都是指从应用程序的缓存->os的缓存->物理设备,如果物理设备没有缓存的话,此时也就相当于持久化成功,但是像磁盘做了raid,raid卡有缓存的话,实际上还没真正持久化成功,因为此时还只到了raid卡的缓存,没到物理设备,但是由于raid卡一般都带有备用电池,所以即使此时断电也不会造成数据丢失。
刚才说了很多时候应用自己也有缓存机制,那么你是否想过此时与os的缓存有重复呢?答案是:会的。刚才说了我是通过研究MySQL的一个参数innodb_flush_method注意这些的,innodb_flush_method表示flush策略,MySQL提供了fdatasync/O_DSYNC/O_DIRECT这三个选项,默认是fdatasync,我这里主要说明为什么会提供选项:O_DIRECT。这个选项告诉os,InnoDB在读写数据的时候都不经过os的缓存,因为刚才说过InnoDB会维护自己的缓存buffer pool,如果还使用os的缓存那么两者就会有一定的重复。有数据表明,如果是大量随机写入操作,O_DIRECT
会提升效率。但是顺序写入和读取效率都会降低。所以使用O_DIRECT需要谨慎。
陶方在《innodb_flush_method带来的性能影响》中从实验角度比较了fdatasync,O_DSYNC和O_DIRECT在性能上的差异。本文将试图从Linux/Unix"文件I/O"(unbuffered I/O)的角度来解释innodb_flush_method是如何影响MySQL的I/O。
innodb_flush_log_at_trx_commit参数确定日志文件何时write、flush。
innodb_flush_method则确定日志及数据文件如何write、flush。
在Linux下,innodb_flush_method可以取如下值:fdatasync, O_DSYNC, O_DIRECT,那这三个值分别是如何影响文件写入的?首先我们需要先来了解Linux的文件I/O是如何工作的。
先来看看Linux/Unix文件I/O的一个典型例子:(Linux 2.6.24测试,gcc编译)
/** A test about syscall of File I/O Author:supu@TaobaoDBA [email protected] http://orczhou.com http://www.taobaodba.com */ #include "stdlib.h" /* for exit */ #include "unistd.h" /* for write fdatasync*/ #include "fcntl.h" /* for open */ int main(void){ int fd; if((fd=open("/home/zzx/test.file",O_WRONLY|O_APPEND|O_DSYNC))<0){ exit(1); } char buff[]="abcdef"; if(write(fd,buff,6)!= 6){ exit(2); } if(fdatasync(fd)==-1){ exit(3); } exit(0); }
程序描述了一般的文件I/O操作的三个过程open、write、fdatasync,分别是打开文件、写文件、flush操作(将文件缓存刷到磁盘上)。
一、Open阶段
open("test.file",O_WRONLY|O_APPDENT|O_SYNC))
系统调用Open会为该进程一个文件描述符fd【附录2】。这里使用了O_WRONLY|O_APPDENT|O_SYNC打开文件:
二、Write阶段
write(fd,buf,6)
在使用open打开文件获得文件描述符之后,我们就可以调用write函数来写入数据了,write会根据前面的open参数不同,而表现不同。
三、Flush阶段
fdatasync(fd) == -1
write操作后,我们还调用了fdatasync来确保文件数据flush到了disk上。fdatasync返回成功后,那么可以认为数据已经写到了磁盘上。像这样的flush的函数还有fsync、sync。
忽略文件打开的过程,通常我们会说“写文件”有两个阶段,一个是调用write我们称为写数据阶段(其实是受open的参数影响),调用fsync(或者fdatasync)我们称为flush阶段。
回到MySQL,参数Innodb_flush_method(Linux)可以设定为:Fdatasync、O_DSYNC、O_DIRECT。我们看看这个三个参数是如何影响程序MySQL对日志和数据文件的操作:
Open log | Flush log | Open datafile | Flush data | |
Fdatasync | fsync() | fsync() | ||
O_DSYNC | O_SYNC | fsync() | ||
O_DIRECT | fsync() | O_DIRECT |
Fsync() |
fdatasync被认为是安全的,因为在MySQL总会调用fsync来flush数据。使用O_DSYNC是有些风险的,有些OS会忽略该参数O_SYNC。
我们看到O_DIRECT和fdatasync和很类似,但是它会使用O_DIRECT
来打开数据文件。