在研究云系统中数据存储的可持续性时,我决定测试一下自己,以确保我理解基本的东西。 我
在这篇文章中,我将探讨 Linux 文件 API 提供的持久存储机制。 看来这里一切都应该很简单:程序调用命令 write()
,该命令完成后,数据将被安全地保存到磁盘。 但 write()
仅将应用程序数据复制到位于 RAM 中的内核缓存。 为了强制系统将数据写入磁盘,需要使用一些额外的机制。
总的来说,本材料是与我所学到的有关我感兴趣的主题的笔记的集合。 如果我们非常简短地谈论最重要的事情,那么事实证明,要组织可持续的数据存储,您需要使用以下命令 fdatasync()
或打开带有该标志的文件 O_DSYNC
。 如果您有兴趣了解更多有关数据从代码到磁盘的过程中发生的情况,请查看
使用 write() 函数的特点
系统调用 write()
标准中定义的 write()
数据读取操作必须准确返回先前写入的字节,即使从其他进程或线程访问数据(
这是否意味着该操作 write()
它是原子的吗? 从技术角度来看,是的。 数据读取操作必须返回全部写入内容或不返回任何内容 write()
。 但操作 write()
根据标准,不一定要以写下要求写下的所有内容来结束。 她只被允许写入部分数据。 例如,我们可能有两个线程,每个线程将 1024 字节附加到由同一文件描述符描述的文件中。 从标准的角度来看,可接受的结果是每次写入操作只能向文件附加一个字节。 这些操作将保持原子性,但完成后,它们写入文件的数据将混合在一起。
fsync() 和 fdatasync() 函数
将数据刷新到磁盘的最简单方法是调用该函数 fdatasync()
。 在 fdatasync()
据称,在该功能运行过程中,磁盘上保存的元数据量“对于正确执行后续数据读取操作是必需的”。 而这正是大多数应用程序所关心的。
这里可能出现的一个问题是,这些机制不能保证在可能发生故障后可以发现该文件。 特别是,在创建新文件时,需要调用 fsync()
对于包含它的目录。 否则,失败后,可能会发现该文件不存在。 原因是在UNIX中,由于硬链接的使用,一个文件可以存在于多个目录中。 因此,调用时 fsync()
文件无法知道哪个目录数据也应该刷新到磁盘(fsync()
到包含相应文件的目录,但其他文件系统可能并非如此。
该机制可以在不同的文件系统上以不同的方式实现。 我用了 fdatasync()
快一点 fsync()
。 公用事业 blktrace
表明 fdatasync()
通常向磁盘写入较少的数据(在 ext4 中 fsync()
写入 20 KiB,并且 fdatasync()
- 16 KiB)。 另外,我发现 XFS 比 ext4 稍快。 在这里,在帮助下 blktrace
设法发现 fdatasync()
将更少的数据刷新到磁盘(XFS 中为 4 KiB)。
使用 fsync() 时出现的歧义情况
我可以想到三种模棱两可的情况 fsync()
这是我在实践中遇到的。
第一起此类案件发生在2008年。 如果大量文件写入磁盘,Firefox 3 界面就会冻结。 问题在于接口的实现使用 SQLite 数据库来存储有关其状态的信息。 每次界面发生变化后,都会调用该函数 fsync()
,为数据的稳定存储提供了良好的保障。 在当时使用的ext3文件系统中,函数 fsync()
将系统中的所有“脏”页面转储到磁盘,而不仅仅是那些与相应文件相关的页面。 这意味着单击 Firefox 中的按钮可能会触发将兆字节的数据写入磁盘,这可能需要很多秒的时间。 据我了解,问题的解决方案
第二个问题发生在2009年。 然后,在系统崩溃后,新 ext4 文件系统的用户面临着许多新创建的文件长度为零的事实,但旧的 ext3 文件系统不会发生这种情况。 在上一段中,我谈到了 ext3 如何将太多数据刷新到磁盘,这导致速度减慢很多。 fsync()
。 为了改善这种情况,在 ext4 中,只有那些与特定文件相关的脏页才会刷新到磁盘。 来自其他文件的数据在内存中保留的时间比 ext3 长得多。 这样做是为了提高性能(默认情况下,数据保持此状态 30 秒,您可以使用以下命令进行配置) fsync()
在需要确保稳定的数据存储并尽可能保护它们免受故障后果的应用程序中。 功能 fsync()
使用 ext4 比使用 ext3 工作效率更高。 这种方法的缺点是,与以前一样,它的使用会减慢某些操作的执行速度,例如安装程序。 查看有关此的详细信息
第三个问题关于 fsync()
,起源于2018年。 然后,在PostgreSQL项目的框架内,发现如果函数 fsync()
遇到错误,它将“脏”页面标记为“干净”。 结果,以下调用 fsync()
他们不会对这些页面做任何事情。 因此,修改后的页面存储在内存中,并且永远不会写入磁盘。 这是一场真正的灾难,因为应用程序会认为某些数据已写入磁盘,但实际上不会。 此类失败 fsync()
很少见,在这种情况下应用程序几乎无法解决该问题。 如今,当这种情况发生时,PostgreSQL 和其他应用程序就会崩溃。 O_SYNC
或带有旗帜 O_DSYNC
。 通过这种方法,系统将报告特定写入操作期间可能发生的错误,但这种方法需要应用程序自行管理缓冲区。 阅读更多相关内容
使用 O_SYNC 和 O_DSYNC 标志打开文件
让我们回到Linux提供稳定数据存储的机制的讨论。 也就是说,我们正在讨论使用标志 O_SYNC
或旗帜 O_DSYNC
使用系统调用打开文件时 write()
系统收到相应的命令 fsync()
и fdatasync()
。 在 write()
и fdatasync()
)。 这种方法的主要缺点是使用相应文件描述符的所有写入都将被同步,这会限制构建应用程序代码的能力。
使用带 O_DIRECT 标志的直接 I/O
系统调用 open()
支持标志 O_DIRECT
,其设计目的是绕过操作系统缓存,通过直接与磁盘交互来执行 I/O 操作。 在许多情况下,这意味着程序发出的写命令将直接转换为旨在操作磁盘的命令。 但是,一般来说,这种机制并不能替代功能 fsync()
или fdatasync()
。 事实上,磁盘本身可以 O_DIRECT
, O_DSYNC
,这意味着每个写操作后面都会有一个调用 fdatasync()
.
原来,XFS文件系统最近为 O_DIRECT|O_DSYNC
-数据记录。 如果使用重写块 O_DIRECT|O_DSYNC
,那么如果设备支持,XFS 将执行 FUA 写入命令,而不是刷新缓存。 我通过使用该实用程序验证了这一点 blktrace
在 Linux 5.4/Ubuntu 20.04 系统上。 这种方法应该更有效,因为使用时,会将最少量的数据写入磁盘,并且使用一个操作,而不是两个操作(写入和刷新缓存)。 我找到了一个链接
sync_file_range() 函数
Linux有一个系统调用 sync_file_range()
据说该团队“非常危险”。 不建议使用它。 特点及危险 sync_file_range()
很好地描述了 fdatasync()
。 在 sync_file_range()
使用 ZFS 时,它不会将数据刷新到磁盘。 经验告诉我,很少使用的代码很可能包含错误。 因此,除非绝对必要,否则我建议不要使用此系统调用。
有助于确保数据持久性的系统调用
我得出的结论是,可以使用三种方法来执行确保数据持久性的 I/O 操作。 它们都需要函数调用 fsync()
用于创建文件的目录。 这些是方法:
- 调用函数
fdatasync()
илиfsync()
后功能write()
(最好使用fdatasync()
). - 使用使用标志打开的文件描述符
O_DSYNC
илиO_SYNC
(更好 - 带标志O_DSYNC
). - 命令用法
pwritev2()
与国旗RWF_DSYNC
илиRWF_SYNC
(最好带有标志RWF_DSYNC
).
性能说明
我没有仔细测量我所研究的各种机制的性能。 我注意到他们的工作速度差异非常小。 这意味着我可能是错的,同一件事在不同的条件下可能会产生不同的结果。 首先我会说什么对性能影响较大,然后什么对性能影响较小。
- 覆盖文件数据比将数据附加到文件更快(性能提升可达 2-100%)。 将数据附加到文件需要对文件的元数据进行额外更改,即使在系统调用之后也是如此
fallocate()
,但这种影响的程度可能会有所不同。 为了获得最佳性能,我建议致电fallocate()
预先分配所需的空间。 那么这个空间必须显式地用零填充并调用fsync()
。 这将确保文件系统中的相应块被标记为“已分配”而不是“未分配”。 这会带来小幅(约 2%)的性能提升。 此外,某些磁盘对块的首次访问可能比其他磁盘慢。 这意味着用零填充空间可以显着(约 100%)提高性能。 特别是,这可能发生在磁盘上亚马逊云服务 (这是非官方数据,我无法证实)。 存储也是如此GCP 持久磁盘 (这已经是官方信息,经过测试证实)。 其他专家也做了同样的事情意见 ,与各种磁盘相关。 - 系统调用越少,性能越高(增益可达到5%左右)。 看起来像是一个挑战
open()
与国旗O_DSYNC
或致电pwritev2()
与国旗RWF_SYNC
比打电话更快fdatasync()
。 我怀疑这里的要点是,这种方法发挥了作用,因为解决同一问题所需执行的系统调用更少(一次调用而不是两次)。 但性能差异非常小,因此您可以完全忽略它并在应用程序中使用不会使其逻辑复杂化的东西。
如果您对可持续数据存储主题感兴趣,这里有一些有用的材料:
I/O 访问方式 ——输入/输出机制的基础知识概述。确保数据到达磁盘 — 一个关于数据从应用程序到磁盘的过程中发生的情况的故事。什么时候应该 fsync 包含目录 - 何时使用问题的答案fsync()
对于目录。 简而言之,事实证明您需要在创建新文件时执行此操作,而此建议的原因是在 Linux 中可能存在对同一文件的多个引用。Linux 上的 SQL Server:FUA 内部结构 ——这里描述了Linux平台上的SQL Server是如何实现持久数据存储的。 这里有一些 Windows 和 Linux 系统调用之间有趣的比较。 我几乎可以肯定,正是通过这份材料,我了解了 XFS 的 FUA 优化。
您是否丢失了您认为安全存储在磁盘上的数据?
来源: habr.com