内存映射文件和单个块的原子写入
Posted
技术标签:
【中文标题】内存映射文件和单个块的原子写入【英文标题】:Memory Mapped files and atomic writes of single blocks 【发布时间】:2011-04-15 03:08:14 【问题描述】:如果我使用普通 IO API 读取和写入单个文件,则可以保证写入在每个块的基础上是原子的。也就是说,如果我的写入只修改了一个块,操作系统保证要么写入整个块,要么什么都不写入。
如何在内存映射文件上实现相同的效果?
内存映射文件只是字节数组,所以如果我修改字节数组,操作系统无法知道我何时认为写入“完成”,因此它可能(即使不太可能)换出内存就在我写块操作的中间,实际上我写了半个块。
我需要某种“进入/离开临界区”,或者在写入文件时将文件的页面“固定”到内存中的某种方法。存在这样的东西吗?如果是这样,是否可以跨常见的 POSIX 系统和 Windows 移植?
【问题讨论】:
有多少应用程序正在与您的映射文件进行交互? 只有一个进程,即数据库服务器。 【参考方案1】:如果我使用普通 IO API 读取和写入单个文件,则可以保证写入是基于每个块的原子操作。也就是说,如果我的写入只修改了一个块,操作系统保证要么写入整个块,要么什么都不写入。
在一般情况下,操作系统不保证使用“普通 IO API”完成的“块写入”是原子的:
块更多是文件系统的概念 - 文件系统的块大小实际上可能映射到多个磁盘扇区... 假设你的意思是扇区,你怎么知道你的写入只映射到一个扇区?当通过文件系统的间接寻址时,没有什么可以说 I/O 与扇区的 I/O 很好地对齐 There's nothing saying your disk HAS to implement sector atomicity。 “真实磁盘”通常可以,但它不是强制性的或保证的属性。遗憾的是,您的程序无法“检查”此属性,除非它是 NVMe 磁盘并且您可以访问原始设备,或者您正在向原始设备发送具有原子性保证的原始命令。此外,您通常关心多个扇区的持久性(例如,如果发生断电,我在该扇区之前发送的数据是否肯定在稳定的存储上?)。如果正在进行任何缓冲,您的写入可能仍然只在 RAM/磁盘缓存中,除非您使用另一个命令先检查/使用 flags requesting cache bypass and said flags were actually honoured 打开文件/设备。
【讨论】:
【参考方案2】:保持journal 的技术似乎是唯一的方法。我不知道这如何与多个应用程序写入同一个文件一起工作。 Cassandra 项目有一个good article,关于如何通过期刊获得绩效。关键是要确保,日志只记录 积极 动作(我的第一种方法是将每次写入的前映像写入日志允许您回滚,但它过度复杂)。
所以基本上你的内存映射文件在标题中有一个transactionId
,如果你的标题适合一个块,你就知道它不会被破坏,尽管很多人似乎用校验和写了两次:[header[cksum]] [header[cksum]]
.如果第一个校验和失败,请使用第二个。
日志看起来像这样:
[beginTxn[txnid]] [offset, length, data...] [commitTxn[txnid]]
您只需继续附加日记记录,直到它变得太大,然后在某个时候将其翻转。当你启动你的程序时,你检查文件的事务 id 是否在日志的最后一个事务 id ——如果不是,你回放日志中的所有事务以同步。
【讨论】:
是的,日记是要走的路,我知道这些算法。但问题是,即使使用日志,你也必须保证数据文件的各个页面只写完整,否则你会冒“半写”页面的风险,并且无法检测到它是否是否已损坏。这就是为什么我正在寻找一种方法来对映射文件中的页面进行原子写入。 为什么这不起作用:partialWrite = (file.transaction-id h2database.com/html/advanced.html#durability_problems) @MartinProbst:您根本无法对页面进行原子写入。我相信这是对 Windows 内核的基本异步操作。您可能想查看FlushFileBuffers
Win API 函数。
非常感谢您提供的信息丰富的回答!双写方法非常出色。您能否参考使用此方法的代码?至于“如果您的标头适合一个块,您就知道它不会损坏”,我不明白为什么会这样,因为该过程可能会在将数据复制到 mmap()ed 内存的过程中崩溃。你能详细说明一下吗?再次感谢您!以上是关于内存映射文件和单个块的原子写入的主要内容,如果未能解决你的问题,请参考以下文章