内存映射映射字节缓冲器还是用于数据库实现的直接字节缓冲器?

由于所有上下文,这看起来像一个很长的问题。下面小说中有2个问题。感谢您抽出宝贵时间阅读本文并提供帮助。

情况

我正在开发一种可扩展的数据存储实现,该实现可以支持在 32 位或 64 位系统上处理大小从几 KB 到 1 TB 或更大的数据文件。

数据存储采用写入时复制设计。始终将新数据或修改后的数据追加到数据文件的末尾,并且从不对现有数据进行就地编辑。

系统可以托管1个或多个数据库;每个都由磁盘上的一个文件表示。

实施的细节并不重要;唯一重要的细节是,我需要不断追加到文件中,并将其从KB增加到MB,再到GB到TB,同时随机跳过文件以进行读取操作以应答客户端请求。

第一个想法

乍一看,我知道我想使用内存映射文件,这样我就可以把有效管理数据的内存状态的负担推到主机操作系统上,然后再从我的代码中推出来。

然后,我的所有代码需要担心的就是在写入时序列化追加到文件的操作,并允许任意数量的同时读取器在文件中查找以应答请求。

设计

由于单个数据文件可以增长到超过MappedByteBuffer的2GB限制,因此我预计我的设计必须包含一个抽象层,该抽象层采用写入偏移并将其转换为特定2GB段内的偏移量。

目前为止,一切都好。。。

问题

这就是我开始挂上去的地方,并认为采用不同的设计(下面提出)可能是更好的方法。

通过阅读SO上大约20个左右的“内存映射”相关问题,似乎mmap调用在分配时想要连续的内存运行很敏感。因此,例如,在32位主机操作系统上,如果我尝试mmap一个2GB文件,由于内存碎片,我的映射成功的可能性很小,相反,我应该使用一系列128MB映射来拉入整个文件。

当我想到这个设计时,甚至说使用1024MB的mmap大小,对于一个DBMS托管几个巨大的数据库,这些数据库都由1TB文件表示,我现在在内存中有数千个内存映射区域,并且在我自己在Windows 7上的测试中试图在多GB文件中创建几百个mmap,我不仅遇到了异常, 实际上,每次我试图分配太多时,我都会将JVM设置为segfault,并且有一次在我的Windows 7机器中剪切并重新初始化了我从未见过的操作系统错误弹出窗口。

无论“你永远不会处理那么大的文件”或“这是一个人为的例子”的论点,事实上,我可以用这些类型的副作用编写这样的东西,这使我的内部警报处于高度警戒状态,并考虑另一种impl(见下文)。

除了这个问题之外,我对内存映射文件的理解是,每次文件增长时,我都必须重新创建映射,因此,对于此仅在设计中追加的文件,它实际上会不断增长。

我可以在一定程度上通过将文件扩展为块(例如一次8MB)来解决这个问题,并且每8MB只重新创建一次映射,但是不断重新创建这些映射的需求让我感到紧张,尤其是在Java中不支持显式取消映射功能的情况下

问题 #1 (共 2 个)

鉴于到目前为止我的所有发现,我认为内存映射文件是主要读取密集型解决方案或只读解决方案的良好解决方案,但考虑到需要不断重新创建映射,因此不是写入密集型解决方案。

但是,然后我环顾周围的环境,使用像MongoDB这样的解决方案,到处都是内存映射文件,我觉得我在这里缺少一些核心组件(我确实知道它一次分配在2GB范围内,所以我想他们正在通过这种逻辑来解决重新映射成本,并帮助维护磁盘上的顺序运行)。

在这一点上,我不知道问题是Java缺乏unmap操作,这使得它更加危险,不适合我的使用,或者我的理解不正确,有人可以指出我北方。

替代设计

上面提出的内存映射设计的替代设计,如果我对mmap的理解是正确的,我将采用如下设计:

定义一个合理可配置大小的直接字节缓冲器(大致为2,4,8,16,32,64,128KB),使其易于与任何主机平台兼容(无需担心DBMS本身会导致抖动场景),并使用原始FileChannel,一次执行文件1缓冲区容量块的特定偏移读取,完全放弃内存映射文件。

缺点是,现在我的代码必须担心诸如“我是否从文件中读取了足够的内容来加载完整的记录?

另一个缺点是,我无法利用操作系统的虚拟内存逻辑,让它自动在内存中为我保留更多“热”数据;相反,我只需要希望操作系统使用的文件缓存逻辑足够大,可以在这里为我做一些有用的事情。

问题 #2 (共 2 个)

我希望得到我对这一切的理解的确认。

例如,也许文件缓存非常棒,在这两种情况下(内存映射或直接读取),主机操作系统将尽可能多地保留我的热数据,并且大文件的性能差异可以忽略不计。

或者,也许我对内存映射文件(连续内存)的敏感要求的理解不正确,我可以忽略所有这些。


答案 1

您可能对 https://github.com/peter-lawrey/Java-Chronicle 感兴趣

在这里,我创建到同一文件的多个内存映射(大小是2到1 GB的幂)文件可以是任何大小(最大到硬盘驱动器的大小)

它还会创建一个索引,以便您可以随机查找任何记录,并且每条记录可以是任何大小。

它可以在进程之间共享,并用于进程之间的低延迟事件。

我假设您正在使用64位操作系统,如果您想使用大量数据。在这种情况下,MappedByteBuffer列表将是您所需要的一切。使用正确的工具完成工作是有意义的。;)

我发现即使数据大小约为主内存大小的10倍,它的性能也很好(我使用的是快速SSD驱动器,所以YMMV)


答案 2

我认为您不必担心大小高达2GB的mmap'ping文件。

将MongoDB的源代码作为DB利用内存映射文件的示例,您会发现它总是在MemoryMappedFile::mapWithOptions()(调用MemoryMappedFile::map())中映射完整的数据文件。数据库数据跨越多个文件,每个文件的大小不超过 2GB。此外,它还可以预先分配数据文件,因此无需随着数据的增长而重新映射,这可以防止文件碎片。通常,您可以使用此数据库的源代码来激发自己的灵感。