传统的UNIX实如今内核中设有缓冲区高速缓存或页面高速缓存,大多数磁盘I/O都经过缓冲进行。当将数据写入文件时,内核一般先将该数据复制到其中一个缓冲区中,若是该缓冲区还没有写满,则并不将其排入输出队列,而是等待其写满或者当内核须要重用该缓冲区以便存放其余磁盘块数据时,再将该缓冲排入输出队列,而后待其到达队首时,才进行实际的I/O操做。这种输出方式被称为延迟写(delayed write)(Bach [1986]第3章详细讨论了缓冲区高速缓存)。
延迟写减小了磁盘读写次数,可是却下降了文件内容的更新速度,使得欲写到文件中的数据在一段时间内并无写到磁盘上。当系统发生故障时,这种延迟可能形成文件更新内容的丢失。为了保证磁盘上实际文件系统与缓冲区高速缓存中内容的一致性,UNIX系统提供了sync、fsync和fdatasync三个函数。
sync函数只是将全部修改过的块缓冲区排入写队列,而后就返回,它并不等待实际写磁盘操做结束。
一般称为update的系统守护进程会周期性地(通常每隔30秒)调用sync函数。这就保证了按期冲洗内核的块缓冲区。命令sync(1)也调用sync函数。
fsync函数只对由文件描述符filedes指定的单一文件起做用,而且等待写磁盘操做结束,而后返回。fsync可用于数据库这样的应用程序,这种应用程序须要确保将修改过的块当即写到磁盘上。
fdatasync函数相似于fsync,但它只影响文件的数据部分。而除数据外,fsync还会同步更新文件的属性。node
对于提供事务支持的数据库,在事务提交时,都要确保事务日志(包含该事务全部的修改操做以及一个提交记录)彻底写到硬盘上,才认定事务提交成功并返回给应用层。数据库
一个简单的问题:在*nix操做系统上,怎样保证对文件的更新内容成功持久化到硬盘?缓存
1 #include <unistd.h> 2 int fsync(int fd);
1 #incude <sys/mman.h>
2 int msync(void *addr, size_t length, int flags)
msync须要指定同步的地址区间,如此细粒度的控制彷佛比fsync更加高效(由于应用程序一般知道本身的脏页位置),但实际上(Linux)kernel中有着十分高效的数据结构,可以很快地找出文件的脏页,使得fsync只会同步文件的修改内容。数据结构
"Unfortunately fsync() will always initialize two write operations : one for the newly written data and another one in order to update the modification time stored in the inode. If the modification time is not a part of the transaction concept fdatasync() can be used to avoid unnecessary inode disk write operations."app
多余的一次IO操做,有多么昂贵呢?根据Wikipedia的数据,当前硬盘驱动的平均寻道时间(Average seek time)大约是3~15ms,7200RPM硬盘的平均旋转延迟(Average rotational latency)大约为4ms,所以一次IO操做的耗时大约为10ms左右。这个数字意味着什么?下文还会提到。less
Posix一样定义了fdatasync,放宽了同步的语义以提升性能:异步
1 #include <unistd.h> 2 int fdatasync(int fd);
"fdatasync does not flush modified metadata unless that metadata is needed in order to allow a subsequent data retrieval to be corretly handled."
在Berkeley DB下,若是开启了AUTO_COMMIT(全部独立的写操做自动具备事务语义)并使用默认的同步级别(日志彻底同步到硬盘才返回),写一条记录的耗时大约为5~10ms级别,基本和一次IO操做(10ms)的耗时相同。
1.每一个log文件固定为10MB大小,从1开始编号,名称格式为“log.%010d"2.每次log文件建立时,先写文件的最后1个page,将log文件扩展为10MB大小3.向log文件中追加记录时,因为文件的尺寸不发生变化,使用fdatasync能够大大优化写log的效率4.若是一个log文件写满了,则新建一个log文件,也只有一次同步metadata的开销