Steins;Lab

  • 项目
  • 折腾
  • 笔记
  • 图册
  • 杂谈
  • 文章索引 - 博主自荐博文
  • 关于/留言
Steins;Lab
某团的自留研究所
  1. 首页
  2. 学习笔记
  3. 正文

(译) 确保数据到达硬盘

2024年9月18日 1988点热度 1人点赞 2条评论

本文翻译自 https://lwn.net/Articles/457667/
原文标题: Ensuring data reaches disk

理想情况下,不会发生操作系统的崩溃和断电,磁盘也不会发生故障。但不幸的是,故障比预期更为常见。本文描述了从应用程序到数据存储的路径。重点介绍数据缓冲部分,提供最佳实践,以确保数据被提交到稳定的持久化存储中,避免发生问题时中途丢失。

本文关注的是 C 语言,但系统调用应该是可以扩展到大多数语言的。

I/O 缓冲

为了实现数据完整性,了解整个系统很重要。数据到达稳定存储之前,经过多个层,如图所示:

最顶层:应用程序。数据位于应用程序的内存块或者缓冲区中。

数据经过的下一层为内核,它保留自己的缓存版本,称为页面缓存 (Page Cache)。脏页(Dirty Page)可以在 Page Cache 中停留不确定的时间,具体取决于系统负载和 I/O 模式。

当脏数据最终从缓存中清除时,会被写入存储设备。存储设备可能也有自己的易失性缓存,如果断电,数据会丢失。最后一层是非易失存储。一旦数据到达这一层,认为是 “安全的”。

考虑一个应用,在网络 socket 上侦听连接,并将数据写入文件。在关闭连接前,这个应用需要确保收到的数据已经写入稳定存储,并向 client 发送确认信息。

 0 int
 1 sock_read(int sockfd, FILE *outfp, size_t nrbytes)
 2 {
 3      int ret;
 4      size_t written = 0;
 5      char *buf = malloc(MY_BUF_SIZE);
 6 
 7      if (!buf)
 8              return -1;
 9
10      while (written < nrbytes) {
11              ret = read(sockfd, buf, MY_BUF_SIZE);
12              if (ret =< 0) {
13                      if (errno == EINTR)
14                              continue;
15                      return ret;
16              }
17              written += ret;
18              ret = fwrite((void *)buf, ret, 1, outfp);
19              if (ret != 1)
20                      return ferror(outfp);
21      }
22 
23      ret = fflush(outfp);
24      if (ret != 0)
25              return -1;
26 
27      ret = fsync(fileno(outfp));
28      if (ret < 0)
29              return -1;
30      return 0;
31 }

第 5 行是应用程序缓冲区的示例。把 socket 读到的数据放入这个 buffer。由于传输的数据量已知,考虑到网络通信可能是突发、缓慢的,我们使用 libc 的 stream 函数 (fwrite() 和 fflush()) 进一步缓冲数据。

10-21 行负责将 socket 读取的数据写入文件流。22 行,所有的数据都写入了文件流。23 行,刷新文件流,数据进入 “内核缓冲区 kernel buffer” 层。27 行,确保数据保存到上图中的 “稳定存储 stable storage” 层

I/O API

将 I/O 分为 3 个不同的类别: System I/O,Stream I/O、内存映射(mmap) I/O。

System I/O:通过系统接口将数据写入仅内核空间可访问的存储层操作。下面的表格是 System 接口的一部分。(表格不完全,重点介绍写入操作)

Operation Function(s)
Open open(), creat()
Write write(), aio_write(), pwrite(), pwritev()
Sync fsync(), sync()
Close close()

Stream I/O 定义:使用 C library 的 stream 接口。使用这些接口可能不会导致系统调用。这意味着调用这些函数后,数据可能仍然存活在用户空间中。下面是 stream 接口列举(不完全):

Operation Function(s)
Open fopen(), fdopen(), freopen()
Write fwrite(), fputc(), fputs(), putc(), putchar(), puts()
Sync fflush(), followed by fsync() or sync()
Close fclose()

内存映射 I/O:与上面 System I/O 类似,仍然使用相同的接口打开、关闭。但对文件数据访问。通过将数据映射到进程的地址空间进行。然后就可以对数据缓冲区一样,执行内存读写操作,从而操作文件。

Operation Function(s)
Open open(), creat()
Map mmap()
Write memcpy(), memmove(), read(), or any other routine that writes to application memory
Sync msync()
Unmap munmap()
Close close()

打开文件时候可以通过 2 个 flag 设置缓存行为 O_SYNC(以及相关的 O_DSYNC) 和 O_DIRECT。

使用 O_DIRECT 打开文件执行 I/O 操作,会绕过内核的页面缓存,直接写入存储。回想一下,存储层本身可能将数据写入缓存中,因此,使用 O_DIRECT 打开的文件,仍然需要 fsync() 才能将数据保存到稳定存储中。O_DIRECT 仅与 System I/O API 相关。

原始设备(/dev/raw/rawN)是一个 O_DIRECT 的特例。这些设备可以不指定 O_DIRECT 打开,仍然提供 direct I/O 的语义。

SYNC I/O:使用 O_SYNC 或 O_DSYNC 打开的文件描述符,执行任何 I/O (包括带 O_DIRECT或者不带的 system I/O)。

以下是 POSIX 定义的 SYNC 模式

标志 描述
O_SYNC 文件数据和所有文件元数据同步写入磁盘。
O_DSYNC 仅将文件数据和访问文件数据所需的元数据同步写入磁盘。
O_RSYNC 未实现。

对这些文件描述符的写入,数据和相关元数据会立即写入稳定存储。注意:检索文件数据不需要的元数据可能不会立即写入,比如访问时间、创建时间、修改时间。

值得指出的是,使用 O_SYNC 和 O_SYNC 与 libc 文件流关联的微妙。请记住,文件指针的 fwrite() 由 lib c 缓冲。直到调用 fflush() 后,才能确定数据写入硬盘。本质上,将文件流与 SYNC 关联,意味着 fflush() 之后,不需要对文件描述符进行 fsync(),但 fflush() 仍然是必要的。

什么时候进行 fsync?

有简单的规则用于确定是否需要调用 fsync()。
首先,您必须回答这个问题:立即将数据保存到稳定存储是否必要?如果是临时数据,您可能不需要 fsync()。反之,如果您需要保存事务结果或者用户配置文件,请使用 fsync()。

更微妙的是处理和创建新的文件、覆盖现有文件。
新创建的文件可能不仅需要对文件进行 fsync(), 也需要对其目录进行 fsync()(这是因为目录是文件系统查找文件的位置)。这个行为取决于操作系统和挂载选项。您可以针对每个文件系统单独编码,也可以对目录执行 fsync() 来确保可移植性。

同样地,如果覆盖文件时候遇到系统故障(例如断电、空间不足ENOSPC、I/O 错误),可能导致现有数据丢失,通常的做法,也是建议的做法,是将更新的数据写入临时文件,确保稳定存储安全,然后将临时文件重命名为原始文件名,从而替换内容。这样可以确保文件是原子更新的,以便其他读取能够获得数据,或者另一份数据。此类更新需要进行以下步骤:

  1. 在同一个文件系统创建一个新的临时文件
  2. 写数据到临时文件
  3. fsync() 临时文件
  4. 重命名临时文件为正确的名字
  5. fsync() 目录

检查错误

当使用库或者内核缓冲写 I/O 时,在 write() 和 fflush() 调用可能不会报告错误。因为数据可能进写入 Page Cache。写入错误通常在调用 fsync(), msync(), close() 时报告。因此检查这些调用的返回值非常重要。

回写缓存(write-back cache)

本节提供了有关磁盘缓存以及操作系统对其控制的常规信息。本节的事项不会影响应用程序的构建方式,因此本节仅供参考。

存储设备上的会写缓存有多种类型。一种是易失的,本文章中一直是假设这种类型。但是,大多数设备可以配置在无缓存或者直写(write-through)模式。在请求到达稳定存储状态前,两者都不会返回成功。外部存储通常由非易失性、或者由电池供电的写缓存,即使断电也会保存数据。但在程序员角度,这些是无法看到的,最好假设磁盘的缓存是易失的,并谨慎编程。在保存数据时,操作系统将执行任何可能的优化,以保持尽可能搞的性能。

某些文件系统提供挂载选项控制缓存刷新行为。对于内核版本 2.6.35 中的 ext3、ext4、xfs 和 btrfs,挂载选项 -o barrier 打开缓存刷新屏障,或者 -o nobarrier 关闭屏障。更老的内核版本可能需要不同的选项(-o barrier=0,1),这取决于文件系统。同样,应用程序编写者不需要考虑这些选项。当文件系统的屏障被禁用时,这意味着 fsync 调用不会导致磁盘缓存刷新。希望管理员在指定此挂载选项之前,知道自己确实是不需要缓存刷新的。

附录:一些例子

本节提供了程序员经常需要执行的示例代码

  1. Synchronizing I/O to a file stream
  2. Synchronizing I/O using file descriptors (system I/O) This is actually a subset of the first example and is independent of the O_DIRECT open flag (so will work whether or not that flag was specified).
  3. Replacing an existing file (overwrite).
  4. sync-samples.h (needed by the above examples).

相关

标签: 存储
最后更新:2024年9月23日

SPtuan

SPtuan 是一名普通的工程师,最大的愿望是度过平静的时光。 当前从事网络/CDN/对象存储研发。

点赞
< 上一篇
0 0 votes
文章评分
Subscribe
Login
提醒
guest

guest

2 评论
最新
最旧 得票最多
Inline Feedbacks
View all comments
SPtuan
SPtuan
作者
1 月 之前

书签:
关于 disk-io 的经验, 异步/同步 io 系统设计的经验
https://youjiali1995.github.io/scylladb/disk-io/

讨论消费级/企业级硬盘的 fsync 性能
https://zhuanlan.zhihu.com/p/55658164

Last edited 1 月 之前 by SPtuan
0
回复
SPtuan
SPtuan
作者
7 月 之前

# 一些讨论

## fsync() 与 sync()

- fsync 作用于单个文件,确保特定文件的数据被写入。
- sync 作用于所有已打开的文件,确保整个系统的文件一致性。

## fflush() 与 fsync()

- fflush 只影响缓冲区,确保数据从用户空间(缓冲区)写入到内核空间(文件系统缓存)。
- fsync 确保数据从内核空间写入到物理存储设备。
- **先调用** fflush:确保所有待写入的数据都已经被写入到内核的文件系统缓存。
- **再调用** fsync:确保这些数据被写入到物理存储设备。

## 为什么 stream 函数都在 open、close 前加 f,f 代表什么

- f 表示“file”,用于标识与文件相关的标准 I/O 函数。
- 这些函数提供了更高层次的文件操作接口,利用了缓冲机制,提高了 I/O 操作的效率和便利性。

## 什么场景使用 mmap 更方便

- mmap 提供了高效的内存访问方式,特别适合处理大文件和需要频繁读写的场景,能够显著提高性能并简化代码。

Last edited 7 月 之前 by SPtuan
0
回复

SPtuan

SPtuan 是一名普通的工程师,最大的愿望是度过平静的时光。
当前从事网络/CDN/对象存储研发。

  • I/O 缓冲
  • I/O API
  • 什么时候进行 fsync?
  • 检查错误
  • 回写缓存(write-back cache)
  • 附录:一些例子
分类
  • Uncategorized
  • 图册
  • 学习笔记
  • 库
  • 折腾
  • 杂谈
  • 瞎**扯
  • 碎碎念
  • 项目跟踪
最近评论
SPtuan 发布于 2 个月前(03月22日) 书签: 关于 disk-io 的经验, 异步/同步 io 系统设计的经验 https://you...
SPtuan 发布于 2 个月前(03月21日) 如果公司不是对外提供这些服务的,这种岗位都是 infra 部门,平均年龄确实会大一些。尤其构建和维护...
HUA 发布于 2 个月前(03月19日) 想请问博主对于国内CDN行业,以及CDN调度、DNS托管类服务相关岗位的看法,以及是否还推荐校招新人...
SPtuan 发布于 3 个月前(02月03日) 2025 注: 长辈对于只身去深圳的担忧,更多地来自于 80s/90s 治安情况。近几年了解了严打...
SPtuan 发布于 4 个月前(01月16日) 哈哈,100就100吧,新年快乐!
热门主题 & 页面
  • 全球互联网拓扑探索 (1) : 互联网是如何工作的
  • [实验]VPS搭建ss服务中转实现纯ipv6访问网络-校园网免流量
  • PYNQ上手体验:以目标检测应用为例
  • Intel Movidius Neural Compute Stick - 英特尔Movidius神经计算棒上手体验
  • iowait 到底是什么?
归档
  • 2025 年 5 月
  • 2025 年 3 月
  • 2024 年 12 月
  • 2024 年 9 月
  • 2024 年 8 月
  • 2024 年 5 月
  • 2024 年 3 月
  • 2024 年 2 月
  • 2023 年 12 月
  • 2023 年 11 月
  • 2023 年 9 月
  • 2023 年 8 月
  • 2023 年 4 月
  • 2023 年 1 月
  • 2022 年 12 月
  • 2022 年 10 月
  • 2022 年 9 月
  • 2022 年 7 月
  • 2022 年 6 月
  • 2022 年 2 月
  • 2021 年 12 月
  • 2021 年 11 月
  • 2021 年 2 月
  • 2021 年 1 月
  • 2020 年 9 月
  • 2020 年 4 月
  • 2020 年 3 月
  • 2020 年 1 月
  • 2019 年 8 月
  • 2019 年 7 月
  • 2019 年 5 月
  • 2019 年 4 月
  • 2019 年 3 月
  • 2019 年 2 月
  • 2018 年 12 月
  • 2018 年 10 月
  • 2018 年 9 月
  • 2018 年 8 月
  • 2018 年 5 月
  • 2018 年 2 月
  • 2018 年 1 月
  • 2017 年 11 月
  • 2017 年 9 月
  • 2017 年 7 月
  • 2017 年 6 月
  • 2017 年 5 月
  • 2017 年 4 月
  • 2017 年 3 月
  • 2017 年 2 月
  • 2017 年 1 月
  • 2016 年 12 月
  • 2016 年 11 月
  • 2016 年 10 月
  • 2016 年 9 月
  • 2016 年 8 月
  • 2016 年 7 月
  • 2016 年 6 月
  • 2016 年 5 月
  • 2016 年 4 月
  • 2016 年 3 月
  • 2016 年 2 月
  • 2016 年 1 月
  • 2015 年 12 月
  • 2015 年 11 月
  • 2015 年 9 月

友情链接:

Blessing Studio hahaschool 绘枫和畅 魔法少女Fandy monsterx Clarke的博客 Luminous’ Home Shintaku's Blog
蓝黑的博客 haruhi.club Yida的博客 Bo2SS 涛叔 TangBao 同和君Hocassian

Steins;Lab 团子神社 zdfmc.net

steinslab.io built with ❤. Thanks for all 2015-2025.

Theme Kratos Made By Seaton Jiang

wpDiscuz