【问题标题】:Memory-Mapped MappedByteBuffer or Direct ByteBuffer for DB Implementation?用于 DB 实现的内存映射 MappedByteBuffer 或 Direct ByteBuffer?
【发布时间】:2012-03-04 21:38:39
【问题描述】:

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

情况

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

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

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

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

第一想法

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

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

设计

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

到目前为止一切都很好......

问题

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

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

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

不管“你永远不可能处理那么大的文件”或“这是一个人为的例子”的论点,我可以用这些类型的副作用编写类似的东西,这让我的内心高度警惕-alert 并考虑另一种 impl(如下)。

除了这个问题,我对内存映射文件的理解是,每次文件增长时我都必须重新创建映射,所以对于这个在设计中只附加的文件,它实际上是在不断增长.

我可以在一定程度上解决这个问题,方法是分块增长文件(比如一次 8MB),并且每 8MB 重新创建一次映射,但是需要不断地重新创建这些映射让我很紧张,尤其是没有显式unmap feature supported in Java

第 1 题(共 2 题)

鉴于到目前为止我的所有发现,我认为内存映射文件是一种很好的解决方案,主要用于读取繁重的解决方案或只读解决方案,但不是写入繁重的解决方案,因为需要重新创建不断映射。

然后我环顾四周,使用诸如 MongoDB 之类的解决方案在所有地方都包含内存映射文件,我觉得我在这里缺少一些核心组件(我确实知道它在 2GB 范围内分配时间,所以我想他们正在使用这种逻辑解决重新映射成本,并帮助维持磁盘上的顺序运行)。

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

另类设计

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

a direct ByteBuffer 定义为合理的可配置大小(大致为 2、4、8、16、32、64、128KB),使其易于与任何主机平台兼容(无需担心 DBMS 本身会导致崩溃的情况) 并使用原始 FileChannel,一次执行文件 1 buffer-capacity-chunk 的specific-offset reads,完全放弃内存映射文件。

缺点是现在我的代码必须担心“我从文件中读取的内容是否足以加载完整记录?”

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

第 2 题(共 2 题)

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

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

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

【问题讨论】:

  • 如果您在提出问题后获得了一些见解,请将它们作为答案发布。很多人读过这个问题,他们可以利用这个洞察力。围绕 mmaping 存在大量“无法修复”的错误,例如 bugs.sun.com/view_bug.do?bug_id=6893654(尽管 JVM 段错误和图形驱动程序崩溃更严重!)有趣的是,一个简单、优雅的本机功能如何在托管世界中变得复杂而丑陋。
  • @AleksandrDubinsky 你是对的(关于优雅变得不优雅)——我的最终发现是,如果不给系统引入明显的不稳定性,就无法快速创建 mmap 文件(我不知道是否我在这个线程中澄清了,但我设法蓝屏了我的 Windows 开发机器)。这个细节单独让我想坚持使用 AsyncFileChannel 进行文件 I/O 并避免一起使用 mmap,尽管 Peter(下)在 Chronicle 中取得了重大成功。
  • @AleksandrDubinsky 一旦我能够将虚拟机和我的机器都带到它的膝盖上,明显“误用”映射文件,我就完成了沿着这条路走下去。它们很优雅并且提供了出色的性能,但是从我在 AsyncFileChannel 上所做的更多阅读来看,您似乎可以获得非常接近相同的性能(允许操作系统利用 FS 和磁盘控制器以及 I/O 排序来优化请求)。如果您真的想走 mmap 路径,这里的专家 Peter 是。

标签: java file-io database-design memory-mapped-files bytebuffer


【解决方案1】:

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

在此我为同一个文件创建多个内存映射(大小为 2 的幂,最大为 1 GB)该文件可以是任意大小(最大为硬盘大小)

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

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

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

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

【讨论】:

  • 不知道你是编年史的作者;谢谢回复。您如何处理对文件的写入,是通过 MBB 还是直接调用 FileChannel,每次读取操作进入时,在进一步 MBB 的范围之外,您只需创建一个新的并将其添加到您的数据缓冲区列表?我缺少的一个核心细节是 lots 的大型映射文件对主机操作系统的内存使用有何影响。 (在下一条评论中继续...)
  • 因为在内存映射文件时似乎需要“连续 ram”,假设我决定使用 64 或 128MB 之类的安全内存,并且随着数据库文件的增长和请求的数据超出现有的映射边界我只是在运行中创建更多。然后假设我的数据文件达到了 100 GB,如果不是 1000 个内存映射字节缓冲区,我也有 100 个……似乎我正在设置我的主机以在 VM 被填满时疯狂地开始分页。我想知道问题和缺点是我所问的症结所在。
  • 每个内存映射文件都有些昂贵(我没有确切的细节)我知道如果您创建大量 1 MB 映射,您很快就会耗尽资源。但是,如果您使用 1 GB 的缓冲区,您可以创建一个 8 TB 的文件。您可以通过创建大量小的(例如 4 KB)来确定对您的系统来说太多了
  • 缓冲区过大不是问题。它仅将您实际使用的页面分配给内存或磁盘。这意味着您可以将其设置为 1 GB 用于数据和索引,但执行 du 并找到它仅使用 8 KB。所以诱惑不是让它们尽可能大。缺点是创建它们很昂贵(有些工作与映射的大小成正比)因此我将它们设置为中等大小,例如 16 MB 或 256 MB,以减少对增长的影响。
  • 我已经研究过在后台线程中增加映射,虽然速度更快,但我发现这会导致随机 BUS 错误。 :( 似乎映射不能立即在创建它的线程中使用。即使在不同的线程中释放它也可能导致崩溃。
【解决方案2】:

我认为您不应该担心 mmap'ping 最大 2GB 的文件。

查看 MongoDB 的源代码作为 DB 使用内存映射文件的示例,您会发现它始终映射 MemoryMappedFile::mapWithOptions()(调用 MemoryMappedFile::map())中的完整数据文件。数据库数据跨越多个文件,每个文件最大 2GB。它还预先分配数据文件,因此随着数据的增长无需重新映射,这可以防止文件碎片。一般来说,你可以通过这个数据库的源代码来启发自己。

【讨论】:

  • @Thomas 我已经更新了链接,但我认为代码已经过时了,从那时起 MongoDB 经历了很多变化。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-12-31
  • 2016-11-13
  • 2014-12-28
  • 1970-01-01
  • 2015-03-15
相关资源
最近更新 更多