当前位置: 首页 > 面试题库 >

用于数据库实现的内存映射MappedByteBuffer或直接ByteBuffer?

颜镜
2023-03-14
问题内容

由于所有上下文,这似乎是一个很长的问题。 下面的小说里有两个问题。感谢您抽出宝贵时间阅读本文并提供帮助。

情况

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

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

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

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

首要思想

乍一看,我知道我想使用内存映射文件,这样我就可以将有效管理数据的内存状态的负担转移到主机OS和我的代码之外。

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

设计

因为单个数据文件的增长量可能超过MappedByteBuffer的2GB限制,所以我希望我的设计必须包括一个抽象层,该抽象层具有写偏移并将其转换为特定2GB段内的偏移。

到目前为止,一切都很好…

问题

这就是我开始挂断电话的地方,并认为采用不同的设计(下面提出)可能是实现此目的的更好方法。

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

当我想到这种设计时,甚至说使用1024MB mmap大小,对于一个DBMS托管一些全部由1TB文件表示的大型数据库,现在我在内存中有 成千上万
的内存映射区域,并且在Windows
7上我自己进行了测试在一个多GB的文件上创建数百个mmap,我不仅遇到了异常,每次尝试分配过多的空间时,实际上我都会让JVM进行段错误,并且在一种情况下,我将Windows
7计算机中的视频切出并用我从未见过的OS错误弹出窗口重新初始化。

不管“您将永远不可能处理那么大的文件”还是“这是人为的例子”,无论我是否可以编写带有此类副作用的代码,这一事实都使我的内部警报响起了高度警惕,考虑使用替代的隐式实现(如下)。

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

我可以通过逐个增加文件大小(每次增加8MB)并在每8MB中重新创建一次映射来在某种程度上解决这个问题,但是不断地重新创建这些映射的需求让我感到紧张,尤其是没有显式取消映射功能时在Java中受支持。

第1个问题,共2个

鉴于到目前为止的所有发现,我将内存映射文件作为主要用于读取大量解决方案或只读解决方案的好解决方案,而不考虑写入大量解决方案,因为需要不断重新创建映射。

但是随后,我使用诸如MongoDB之类的解决方案环顾了周围的环境,这些解决方案到处都包含内存映射文件,我感觉我在这里缺少了一些核心组件(我确实知道它一次分配2GB扩展区,所以我想他们正在使用这种逻辑来解决重新映射的成本,并有助于在磁盘上维持顺序运行。

在这一点上,我不知道问题出在Java是否缺少取消映射操作,从而使此操作更加危险并且不适合我的使用,或者我的理解不正确并且有人可以将我指北。

替代设计

如果我对mmap的理解是正确的,我将采用上面提出的内存映射设计的另一种设计,如下所示:

定义一个具有合理可配置大小(大约2、4、8、16、32、64、128KB)的直接ByteBuffer,使其易于与任何主机平台兼容(不必担心DBMS本身会引起崩溃的情况)并使用原始FileChannel一次执行文件1缓冲容量块的特定偏移读取,完全放弃了内存映射文件。

不利的一面是,现在我的代码必须担心诸如“我是否从文件中读取了足够的内容以加载完整的记录?”之类的东西。

另一个缺点是,我没有利用操作系统的虚拟内存逻辑,而是让它自动为我保留了更多“热”数据在内存中。相反,我只希望操作系统使用的文件缓存html" target="_blank">逻辑足够大,可以在这里对我有所帮助。

第2个问题,共2个问题

我希望能证实我对所有这些的理解。

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

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


问题答案:

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

在此方法中,我创建了到同一文件的多个内存映射(大小为2的幂,最大为1 GB)。文件可以为任意大小(最大为硬盘驱动器的大小)

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

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

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

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



 类似资料:
  • 虚拟内存是一种方便的方法,可以在进程之间隔离内存,并为每个进程提供自己的地址空间。它通过将虚拟地址转换为物理地址来工作。 我已经非常熟悉虚拟内存的工作原理和实现。我不知道的是虚拟内存相对于直接映射内存的性能影响,直接映射内存不需要翻译开销。 请不要说没有开销。这显然是错误的,因为遍历页表需要多次内存访问。TLB未命中可能很少,因此对性能的影响可以忽略不计,但是,如果是这种情况,应该有证据证明这一点

  • 系统调用在调用进程的虚拟地址空间中提供映射,将文件或设备映射到内存中。 下面是两种类型 - 文件映射或文件支持的映射 - 此映射将进程的虚拟内存区域映射到文件。 这意味着读取或写入这些内存区域会导致文件被读取或写入。这是默认的映射类型。 匿名映射 - 此映射映射进程的虚拟内存区域,不受任何文件的支持。 内容被初始化为零。 这种映射类似于动态内存分配(malloc()),在某些实现中用于某些分配。

  • 实现内核重映射 在上文中,我们虽然构造了一个简单映射使得内核能够运行在虚拟空间上,但是这个映射是比较粗糙的。 我们知道一个程序通常含有下面几段: .text 段:存放代码,需要可读、可执行的,但不可写; .rodata 段:存放只读数据,顾名思义,需要可读,但不可写亦不可执行; .data 段:存放经过初始化的数据,需要可读、可写; .bss 段:存放零初始化的数据,需要可读、可写。 我们看到各个

  • 我正在用Python实现一个Trie。到目前为止,我遇到了两种不同的方法来实现它: -存储字符 -存储单词结尾(true或false) -存储具有当前前缀的单词数 我们派生出这本词典: 哪一种是高效的、标准的数据结构,既能有效地存储内存,又能快速地进行字的大数据集上的遍历和其他trie操作?

  • yaf项目快速开发(兼容php7): yaf project rapid development, integration of the db action class support chain operation, support separate read and write, pdo, mysqli, mongo, upload operation, adding redis operat