本文转载自Linux/UNIX编程如何保证文件落盘

导语

我们编写程序write数据到文件中时,其实数据不会立马写入磁盘,而是会经过层层缓存。每层缓存都有自己的刷新时机,每层缓存都刷新后才会写入磁盘。这些缓存的存在是为了加速读写操作,因为如果每次读写都对应真实磁盘操作,那么读写的效率会大大降低。带来的坏处是如果期间发生掉电或者别的故障,还未写入磁盘的数据就丢失了。对于数据安全敏感的应用,比如数据库,比如交易程序,这是无法忍受的。所以操作系统提供了保证文件落盘的机制。我们来看下这些机制的原理和使用。

I/O缓冲区机制



图片来自:https://lwn.net/Articles/457667/

上图说明了操作系统到磁盘的数据流,以及经过的缓冲区。首先数据会先存在于应用的内存空间,如果调用库函数写入,库函数可能还会把数据缓存在库函数所维护的缓冲区空间中,比如C标准库stdio提供的方法就会进行缓存,目的是为了减少系统调用的次数。这两个缓存都是在用户空间中的。库函数缓存刷新时,会调用write系统调用写入内核空间,内核同样维护了一个页缓存(page cache),操作系统会在合适的时间把脏页的数据写入磁盘。即使是写入磁盘了,磁盘也可能维护了一个缓存,在这个时候掉电依然会丢失数据的,只有写入了磁盘的持久存储物理介质上,数据才是真正的落盘了,是安全的。我们接下来就是要研究如何做到这一步。

用户空间缓冲区

用户空间的缓存分为应用程序本身维护的缓冲区与库维护的缓冲区。

应用本身维护的缓冲区需要开发者自己刷新,调用库函数写入到库函数的缓冲区中。如果应用程序不依赖任何库函数,而是直接使用系统调用,那么则是把数据写入系统的缓冲区去。

库函数一般都会维护缓冲区,目的是简化应用程序的编写,应用程序就不需要编写维护缓冲区的代码,同时性能也得到了提高,因为缓冲区大大减少了系统调用的次数,而系统调用是非常耗时的,系统调用涉及到用户态到内核态的切换,这个切换需要很多的步骤与校验,较为耗时。

比如C标准库stdio就维护着一个缓冲区,对应这个缓冲区,C标准库提供了fflush方法强制把缓冲区数据写入操作系统。

Java的OutputStream接口提供了一个flush方法,具体的作用要看实现类的具体实现。BufferedOutputStream#flush就会把自己维护的缓冲区数据写入下一层的OutputStream。如果是new BufferedOutputStream(new FileOutputStream("/"))这样的模式,则调用BufferedOutputStream#flush会将数据写入操作系统。

内核缓冲区

应用程序直接或者通过库函数间接的使用系统调用write将数据写入操作系统缓冲区。

UNIX系统在内核中设有高速缓存或页面高速缓存。目的是为了减少磁盘读写次数。

用户写入系统的数据先写入系统缓冲区,系统缓冲区写满后,将其排入输出队列,然后得到队首时,才进行实际的IO操作。这种输出方式被称为延迟写

UNIX系统提供了三个系统调用来执行刷新内核缓冲区:syncfsyncfdatasync

sync

void sync(void)

sync函数只是将所有修改过的块缓冲区排入输出队列就返回,并不等待实际的写磁盘操作返回。

操作系统的update系统守护进程会周期地调用sync函数,来保证系统中的数据能定期落盘。

根据sync(2) - Linux manual page的描述,Linux对sync的实现与POSIX规范不太一样,POSIX规范中,sync可能在文件真正落盘前就返回,而Linux的实现则是文件真正落盘后才会返回。所以Linux中,syncfsync的效果是一样的!但是1.3.20之前的Linux存在BUG,导致sync并不会在真正落盘后返回。

fsync

void fsync(int filedes)

fsync对指定的文件起作用,它传输内核缓冲区中这个文件的数据到存储设备中,并阻塞直到存储设备响应说数据已经保存好了。

fsync对文件数据与文件元数据都有效。文件的元数据可以理解为文件的属性数据,比如文件的更新时间,访问时间,长度等。

fdatasync

void fdatasync(int filedes)

fdatasyncfsync类似,两者的区别是,fdatasync不一定需要刷新文件的元数据部分到存储设备。

是否需要刷新文件的元数据,是要看元数据的变化部分是否对之后的读取有影响,比如文件元数据的访问时间st_atime和修改时间st_mtime变化了,fdatasync不会去刷新元数据数据到存储设备,因为即使这个数据丢失了不一致了,也不影响故障恢复后的文件读取。但是如果文件的长度st_size变化了,那么就需要刷新元数据数据到存储设备。

所以如果你每次都更新文件长度,那么调用fsyncfdatasync的效果是一样的。

但是如果更新能做到不修改文件长度,那么fdatasync能比fsync少了一次磁盘写入,这个是非常大的速度提升。

O_SYNCO_DSYNC

除了上面三个系统调用,open系统调用在打开文件时,可以设置和同步相关的标志位:O_SYNCO_DSYNC

设置O_SYNC的效果相当于是每次write后自动调用fsync

设置O_DSYNC的效果相当于是每次write后自动调用fdatasync

关于新建文件

在一个文件上调用fsync/fdatasync只能保证文件本身的数据落盘,但是对于文件系统来说,目录中也保存着文件信息,fsync/fdatasync的调用并不会保证这部分的数据落盘。如果此时发生掉电,这个文件就无法被找到了。

所以对于新建文件来说,还需要在父目录上调用fsync

关于覆盖现有文件

覆盖现有文件时,如果发生掉电,新的数据是不会写入成功,但是可能会污染现有的数据,导致现有数据丢失。

所以最佳实践是新建一个临时文件,写入成功后,再替换原有文件。具体步骤:

  1. 新建一个临时文件
  2. 向临时文件写入数据
  3. 对临时文件调用fsync,保证数据落盘。期间发生掉电对现有文件无影响。
  4. 重命名临时文件为目标文件名
  5. 对父目录调用fsync

存储设备缓冲区

存储设备为了提高性能,也会加入缓存。高级的存储设备能提供非易失性的缓存,比如有掉电保护的缓存。但是无法对所有设备做出这种保证,所以如果数据只是写入了存储设备的缓存的话,遇到掉电等故障,依然会导致数据丢失。

对于保证数据能保存到存储设备的持久化存储介质上,而不管设备本身是否有易失性缓存,操作系统提供了write barriers这个机制。

开启了write barriers的文件系统,能保证调用fsync/fdatasync数据持久化保存,无论是否发生了掉电等其他故障,但是会导致性能下降。

许多文件系统提供了配置write barriers的功能。比如ext3, ext4, xfsbtrfsmount参数-o barrier表示开启写屏障,调用fsync/fdatasync能保证刷新存储设备的缓存到持久化介质上。-o nobarrier则表示关闭写屏障,调用fsync/fdatasync无法保证数据落盘。

Linux默认开启写屏障,所以默认情况下,我们调用fsync/fdatasync,就可以认为是文件真正的可靠落盘了。

对于这个层面的数据安全保证来说,应用程序是不需要去考虑的,因为如果这台机器的硬盘被挂载为没有开启写屏障,那么可以认为这个管理员知道这个风险,他选择了更高的性能,而不是更高的安全性。

总结

  • 文件数据从应用程序写入磁盘,需要经过多个缓冲区:应用本身的缓冲区,库的缓冲区,操作系统缓冲区,磁盘缓冲区
  • 如果文件数据只是写入缓冲区,而还未写入硬盘的持久化存储设备上,那么断电等故障会导致数据丢失
  • 库层面刷新缓冲区:C标准库的fflush,JDK的OutputStream#flush
  • 操作系统层面刷新缓冲区:
    • fsync可以刷新文件数据+元数据缓冲区
    • fdatasync可以刷新文件数据,在不影响读取的情况下,可以不刷新文件元数据,性能更好一些
    • open系统调用的O_SYNC标志位可以在每次write后自动调用fsync
    • open系统调用的O_DSYNC标志位可以在每次write后自动调用fdatasync
  • 存储设备层面刷新缓冲区:文件系统支持开启/关闭写屏障write barriers,如果开启写屏障,则fsync/fdatasync可以保证文件写入磁盘的持久化设备中,如果关闭写屏障,则fsync/fdatasync只能保证文件写入磁盘,此时文件可能存在于磁盘的缓存中

参考资料

Linux/UNIX编程如何保证文件落盘的更多相关文章

  1. Java如何保证文件落盘?

    本文转载自Java如何保证文件落盘? 导语 在之前的文章Linux/UNIX编程如何保证文件落盘中,我们聊了从应用到操作系统,我们要如何保证文件落盘,来确保掉电等故障不会导致数据丢失.JDK也封装了对 ...

  2. Linux/UNIX编程:使用C语言实现简单的 ls 命令

    刚好把 Linux/UNIX 编程中的文件和IO部分学完了,就想编写个 ls 命令练习一下,本以为很简单,调用个 stat 就完事了,没想到前前后后弄了七八个小时,90%的时间都用在格式化(像 ls ...

  3. 学习linux/unix编程方法的建议(转)

    假设你是计算机科班出身,计算机系的基本课程如数据结构.操作系统.体系结构.编译原理.计算机网络你全修过 我想大概可以分为4个阶段,水平从低到高从安装使用=>linux常用命令=>linux ...

  4. 学习linux/unix编程方法的建议,学习Linux的四个步骤(转)

    解答:学习Linux的四个步骤假设你是计算机科班出身,计算机系的基本课程如数据结构.操作系统.体系结构.编译原理.计算机网络你全修过我想大概可以分为4个阶段,水平从低到高从安装使用=>linux ...

  5. 在linux/unix中查找大文件

    在linux/unix中查找大文件,如查找大于100M文件的位置路径,查找等于10M文件的位置路径等等,下面就介绍几个实现快速查找的命令: 1. 查找指定目录下所有大于100M的文件,命令为 find ...

  6. Linux系统:保证数据安全落盘

    在很多IO场景中,我们经常需要确保数据已经安全的写到磁盘上,以便在系统宕机重启之后还能读到这些数据.但是我们都知道,linux系统的IO路径还是很复杂的,分为很多层,每一层都可能会有buffer来加速 ...

  7. Linux:保证数据安全落盘

    背景 在很多IO场景中,我们经常需要确保数据已经安全的写到磁盘上,以便在系统宕机重启之后还能读到这些数据.但是我们都知道,linux系统的IO路径还是很复杂的,分为很多层,每一层都可能会有buffer ...

  8. Linux/Unix编程中的线程安全问题【转】

    转自:http://blog.csdn.net/zhengzhoudaxue2/article/details/6432984 在目前的计算机科学中,线程是操作系统调度的最小单元,进程是资源分配的最小 ...

  9. [linux]Socket编程的头文件

    socket编程中需要用到的头文件 sys/types.h:数据类型定义 sys/socket.h:提供socket函数及数据结构 netinet/in.h:定义数据结构sockaddr_in arp ...

随机推荐

  1. 详解Java8特性之新的日期时间 API

    详解Java8特性之新的日期时间 API http://blog.csdn.net/timheath/article/details/71326329 Java8中时间日期库的20个常用使用示例 ht ...

  2. Pythonchallenge1过关攻略

    第一关上来是一个电视,上面写着2^38,这就非常关键了,这时候我们已经有了大致思路,再看一眼电视机下面的话确认一下,"Hint: try to change the URL address. ...

  3. hdu1517 A Multiplication Game

    Time Limit: 5000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Submission ...

  4. Codeforces Round #552 (Div. 3) C. Gourmet Cat (数学,模拟)

    题意:你要带着你的喵咪一起去旅行,你的喵在星期\(1,4,7\)吃喵粮\(x\),在星期\(2,6\)吃喵粮\(y\),在星期\(3,5\)吃喵粮\(z\),你只有\(a\)个\(x\),\(b\)个 ...

  5. Codeforces Round #658 (Div. 2) C1. Prefix Flip (Easy Version) (构造)

    题意:给你两个长度为\(n\)的01串\(s\)和\(t\),可以选择\(s\)的前几位,取反然后反转,保证\(s\)总能通过不超过\(3n\)的操作得到\(t\),输出变换总数,和每次变换的位置. ...

  6. 8.PowerShell DSC之Push

    前言 LCM的默认mode就是push,所以对于push模式,我们直接就三步走 以下是示例: 1.编写配置 Authoring Configuration WebsiteTest { # Import ...

  7. 数据可视化 -- Python

    前提条件: 熟悉认知新的编程工具(jupyter notebook) 1.安装:采用pip的方式来安装Jupyter.输入安装命令pip install jupyter即可: 2.启动:安装完成后,我 ...

  8. 5.2 spring5源码--spring AOP源码分析三---切面源码分析

    一. AOP切面源码分析 源码分析分为三部分 1. 解析切面 2. 创建动态代理 3. 调用 源码的入口 源码分析的入口, 从注解开始: 组件的入口是一个注解, 比如启用AOP的注解@EnableAs ...

  9. 4.安装fluentd用于收集集群内部应用日志

    作者 微信:tangy8080 电子邮箱:914661180@qq.com 更新时间:2019-06-13 11:02:14 星期四 欢迎您订阅和分享我的订阅号,订阅号内会不定期分享一些我自己学习过程 ...

  10. 蓝桥杯-摔手机问题【dp】

    非常详细的题解:戳这里 例题:poj-3783 Balls Balls Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 115 ...