eaglet

本博专注于基于微软技术的搜索相关技术
随笔 - 189, 文章 - 0, 评论 - 3725, 阅读 - 147万
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

文件是否真的写入了磁盘?

Posted on   eaglet  阅读(10559)  评论(24编辑  收藏  举报

作者:eaglet

引用请注明出处

 

写文件后调用 FileStream.Close; FileStream.Flush; 或者 using (FileStream fs = new FileStream(…)) {} ,文件是否被实际写入了磁盘?可能大多数人都会说肯定会写入磁盘,但我要告诉你,不一定!

 

背景

我所在的公司有上千台的计算机在同时运行我们的系统,在实际运行过程中,我们发现有时候我们写入的文件会出现全0或者部分全0的情况,但程序中可以肯定的是我们已经关闭了文件句柄。这个问题困扰了我很久。它的发生概率大概在几千分之一,而且大部分是出现在机器重启时,也就是我们更新软件后要求机器自动重启,结果起来后发现更新的软件中有部分文件的大小是对的,但数据全是0。

问题分析

首先考虑的是不是程序的bug,但分析下来,程序没有任何问题,我们甚至用了 File.WriteAllBytes 这样的静态函数来写文件,依然会出现这个问题。

其次考虑的是不是磁盘缓存造成?因为windows操作系统每个磁盘上都可以设置 Enable write caching on the device. 如果打开这个开关,写入操作将先写入磁盘的缓存,然后在到达大小或时间门限时才写入物理磁盘。如果内容还没有完全写入磁盘就重启计算机,就会造成数据丢失。设置如下图所示:

 

image

 

于是把这个功能取消,结果发现问题依旧。

这到底是怎么回事?

放狗搜索后发现 windows 除了在磁盘的硬件级别上可以提供缓存外,在操作系统层面也有一个文件缓存

这个功能叫  File Caching, 是 windows 2000 以后提供的功能。如下图所示:

 

File data caching process

 

当进程写磁盘时,文件会根据一定的策略缓存到系统的文件缓存中,达到一定门限后才会写入物理磁盘。由于这个系统文件缓存对应用程序是透明的,我们在应用程序中调用 文件的 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去写入文件并且每次写入文件的内容都较小,这时确实会有影响。因为每次写入都会触发一次物理的磁盘写。

编辑推荐:
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· SQL Server 2025 AI相关能力初探
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
历史上的今天:
2010-09-13 Bigtable: Google 的结构化数据分布式存储系统 (二)翻译
点击右上角即可分享
微信分享提示