文件是否真的写入了磁盘?
2012-10-31 17:12
162 查看
写文件后调用 FileStream.Close; FileStream.Flush; 或者 using (FileStream fs = new FileStream(…)) {} ,文件是否被实际写入了磁盘?可能大多数人都会说肯定会写入磁盘,但我要告诉你,不一定!
其次考虑的是不是磁盘缓存造成?因为windows操作系统每个磁盘上都可以设置 Enable write caching on the device. 如果打开这个开关,写入操作将先写入磁盘的缓存,然后在到达大小或时间门限时才写入物理磁盘。如果内容还没有完全写入磁盘就重启计算机,就会造成数据丢失。设置如下图所示:
于是把这个功能取消,结果发现问题依旧。
这到底是怎么回事?
放狗搜索后发现 windows 除了在磁盘的硬件级别上可以提供缓存外,在操作系统层面也有一个文件缓存
这个功能叫 File Caching, 是 windows 2000 以后提供的功能。如下图所示:
当进程写磁盘时,文件会根据一定的策略缓存到系统的文件缓存中,达到一定门限后才会写入物理磁盘。由于这个系统文件缓存对应用程序是透明的,我们在应用程序中调用 文件的 Close, Flush 只能保证文件已经被写入了操作系统的文件缓存,但无法保证文件实际被写入了磁盘。这个机制虽然提供了较好的写入性能,但却增加了丢失数据的风险。从应用角度,我们从逻辑上认为写入已经成功,但实际上并没有写入到实际的磁盘,也就是说写入是否真的成功了,软件无从知道,这样带来很多逻辑上的混乱。特别是一些服务进程利用文件锁来控制多个进程锁定的,比如 lucene.net, mongodb 等,就经常出现重启后文件锁锁定出问题的情况,估计也和这个机制的作用有关。
那么这个机制的优点到底在哪里呢?
微软提供这个机制当然是有原因的,他的最大优点是大大提高了读取的性能。我们可以做如下的实验:
当我们打开一个大文件,并顺序读取这个文件,我们发现系统开机后,第一次读取的速度是非常慢的,这个速度主要取决于磁盘的读取速度,因为第一次读取是没有缓存的。但当我们关闭进程,再重新运行进程读取这个大文件时,无论是顺序读取还是随机读取,都比原来快上百倍,这就是因为这个操作系统缓存在里面起了作用,数据是从内存读取的。由于这个缓存是全局的,进程退出后,文件的缓存并没有被清空。我所做的开源全文索引项目 HubbleDotNet 的较新版本就充分利用了这个机制,大大提高了机器重启后首次读取索引的速度。
关于windows操作系统的文件缓存机制以及如何优化不在本文的讨论范围内,我将在以后的文章中专门来讲述这个机制是如何工作的。
我们有没有办法关闭这个文件缓存呢?答案是否定的。但幸运的是 windows 为应用提供了一个标志叫 FILE_FLAG_WRITE_THROUGH, 这个标志可以让应用在写入缓存的同时直接写入磁盘。
用 C# 实现的代码如下:
在程序中做了如上更改后,2000多台机器运行了半年,没有发现一次文件数据丢失的问题(原来几乎每个月会出现几次),基本可以证明这个机制有效。
我们再看一下本文最上面的那个图,磁盘缓存有两个 CheckBox, 第一个是是否打开磁盘缓存,第二个是是否关闭windows 的文件写缓存刷新磁盘。如果第二个选中,则有可能会出现数据丢失,如果没有选中则不会。
默认设置,这个地方是不选中的。从实际测试来看,磁盘缓存也确实不影响数据丢失的问题。
2. 采用 WriteThrough 后是否会降低写入磁盘的性能。
我认为如果是随机写,可能是会有影响的,但如果是顺序写,FileStream 这个类已经提供了缓存功能,并不会有太大的影响。除非你直接调用windows 的文件API去写入文件并且每次写入文件的内容都较小,这时确实会有影响。因为每次写入都会触发一次物理的磁盘写。
http://www.cnblogs.com/eaglet
背景
我所在的公司有上千台的计算机在同时运行我们的系统,在实际运行过程中,我们发现有时候我们写入的文件会出现全0或者部分全0的情况,但程序中可以肯定的是我们已经关闭了文件句柄。这个问题困扰了我很久。它的发生概率大概在几千分之一,而且大部分是出现在机器重启时,也就是我们更新软件后要求机器自动重启,结果起来后发现更新的软件中有部分文件的大小是对的,但数据全是0。问题分析
首先考虑的是不是程序的bug,但分析下来,程序没有任何问题,我们甚至用了 File.WriteAllBytes 这样的静态函数来写文件,依然会出现这个问题。其次考虑的是不是磁盘缓存造成?因为windows操作系统每个磁盘上都可以设置 Enable write caching on the device. 如果打开这个开关,写入操作将先写入磁盘的缓存,然后在到达大小或时间门限时才写入物理磁盘。如果内容还没有完全写入磁盘就重启计算机,就会造成数据丢失。设置如下图所示:
于是把这个功能取消,结果发现问题依旧。
这到底是怎么回事?
放狗搜索后发现 windows 除了在磁盘的硬件级别上可以提供缓存外,在操作系统层面也有一个文件缓存
这个功能叫 File Caching, 是 windows 2000 以后提供的功能。如下图所示:
当进程写磁盘时,文件会根据一定的策略缓存到系统的文件缓存中,达到一定门限后才会写入物理磁盘。由于这个系统文件缓存对应用程序是透明的,我们在应用程序中调用 文件的 Close, Flush 只能保证文件已经被写入了操作系统的文件缓存,但无法保证文件实际被写入了磁盘。这个机制虽然提供了较好的写入性能,但却增加了丢失数据的风险。从应用角度,我们从逻辑上认为写入已经成功,但实际上并没有写入到实际的磁盘,也就是说写入是否真的成功了,软件无从知道,这样带来很多逻辑上的混乱。特别是一些服务进程利用文件锁来控制多个进程锁定的,比如 lucene.net, mongodb 等,就经常出现重启后文件锁锁定出问题的情况,估计也和这个机制的作用有关。
那么这个机制的优点到底在哪里呢?
微软提供这个机制当然是有原因的,他的最大优点是大大提高了读取的性能。我们可以做如下的实验:
当我们打开一个大文件,并顺序读取这个文件,我们发现系统开机后,第一次读取的速度是非常慢的,这个速度主要取决于磁盘的读取速度,因为第一次读取是没有缓存的。但当我们关闭进程,再重新运行进程读取这个大文件时,无论是顺序读取还是随机读取,都比原来快上百倍,这就是因为这个操作系统缓存在里面起了作用,数据是从内存读取的。由于这个缓存是全局的,进程退出后,文件的缓存并没有被清空。我所做的开源全文索引项目 HubbleDotNet 的较新版本就充分利用了这个机制,大大提高了机器重启后首次读取索引的速度。
关于windows操作系统的文件缓存机制以及如何优化不在本文的讨论范围内,我将在以后的文章中专门来讲述这个机制是如何工作的。
解决方案
那么回到这个问题我们有没有办法关闭这个文件缓存呢?答案是否定的。但幸运的是 windows 为应用提供了一个标志叫 FILE_FLAG_WRITE_THROUGH, 这个标志可以让应用在写入缓存的同时直接写入磁盘。
用 C# 实现的代码如下:
using (System.IO.FileStream fs = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None, 8192, FileOptions.WriteThrough))
在程序中做了如上更改后,2000多台机器运行了半年,没有发现一次文件数据丢失的问题(原来几乎每个月会出现几次),基本可以证明这个机制有效。
深入问题:
1. 采用 WriteThrough 后没有关闭磁盘缓存,会不会造成数据丢失?我们再看一下本文最上面的那个图,磁盘缓存有两个 CheckBox, 第一个是是否打开磁盘缓存,第二个是是否关闭windows 的文件写缓存刷新磁盘。如果第二个选中,则有可能会出现数据丢失,如果没有选中则不会。
默认设置,这个地方是不选中的。从实际测试来看,磁盘缓存也确实不影响数据丢失的问题。
2. 采用 WriteThrough 后是否会降低写入磁盘的性能。
我认为如果是随机写,可能是会有影响的,但如果是顺序写,FileStream 这个类已经提供了缓存功能,并不会有太大的影响。除非你直接调用windows 的文件API去写入文件并且每次写入文件的内容都较小,这时确实会有影响。因为每次写入都会触发一次物理的磁盘写。
http://www.cnblogs.com/eaglet
相关文章推荐
- 文件是否真的写入了磁盘?
- 文件是否真的写入了磁盘?
- 文件是否真的写入了磁盘?
- 检测文件是否真的写入了磁盘?
- 文件磁盘相关函数[20]-文本文件读写-分列写入
- ElasticSearch58:内核原理揭秘_最后优化写入流程实现海量磁盘文件合并(segment merge, optimize)
- java 写入文件没有到磁盘问题
- Android一行代码实现将Log写入文件(可配置是否输出Log,测试包可直接用于发布)
- win2003如何为共享文件所在的磁盘进行配额保障数据正常写入
- 源程序Bwriter.java,从键盘输入一系列字符串,写入到某磁盘文件中。
- java判断文件是否正在写入
- jffs2文件系统磁盘写入
- 文件磁盘相关函数[22]-文本文件读写-不同数据类型的写入
- 没有该栏目数据 可能缓存文件(data/cache/inc_catalog_base.inc)没有更新请检查是否有写入权限
- 从键盘输入一系列字符串,写入磁盘文件中
- 通用 文件保存至数据库,从数据库写入磁盘 程序代码
- 文件磁盘相关函数[6]-判断文件夹是否存在 DirectoryExists
- 磁盘挂载后写入不了文件
- 同步所有已挂载的文件系统。所有缓存中的数据将被立刻写入磁盘
- 从磁盘中读入一个文件并写入另一个文件