使用Java中的并发性在光盘上创建键值存储

时间:2011-08-03 18:20:41

标签: java file file-io concurrency file-mapping

我需要读取一组文件并将其分解为键值对,并将这些文件保存为光盘上该键的(键,值列表),就像map-reduce范例一样。但是一切都在一台电脑上。例如,我可以在不同文件上写入不同的列表,并使用密钥命名文件。这似乎是一种非常糟糕的做事方式。首先,如果你有十亿个密钥,你将得到十亿个文件。显然这不会起作用,我需要某种内存映射。我还必须有不同的线程来做map工作,所以如果他们要写入这个相同的缓冲区,它们之间必须有某种同步。如果我有一个键值缓冲区映射,并通过缓冲区同步,那么线程不应该踩到彼此的脚趾,所以我认为该部分应该工作。问题是如何将值映射到光盘。如何编写与同一文件中不同键对应的缓冲区?如果有人能指出我正确的方向,我将不胜感激。我对这方面的了解非常可怜。再次感谢。

4 个答案:

答案 0 :(得分:5)

从实际的角度来看,使用BerkeleyDB很容易做到这一点as Lirik suggested.

如果你对理论比对实践更感兴趣,我建议你将其作为“外部排序”操作。也就是说,尽可能多地读入内存中的输入,然后按键排序。将排序的块作为单个文件写出。然后可以将排序后的文件轻松合并到一个文件中。

在其他应用程序中,这是Lucene用于构建用于搜索文本的“倒排索引”的方法。 “键”是文档中的单词,“值”是单词出现的文档列表。 Lucene读取文档,并为每个单词在内存中创建一个术语到文档的条目。当内存已满时,它会将索引段写入磁盘。当磁盘上有很多索引段时,它们会合并为一个段。事实上,你也可以让Lucene的索引编写器适应你的任务。

可以将工作划分为多个线程。但是,您必须对磁盘争用敏感。跳过同时读取和写入许多文件将大大减慢传统驱动器的速度。可能有机会同时安排一些活动。在将先前排序的块写入磁盘时,您可能会从一个文件中读取新数据,尤其是在计算机有两个磁盘驱动器的情况下。当然,使用SSD临时存储某些已排序的段会有很大帮助。

答案 1 :(得分:4)

我认为Oracle's Berkeley DB可能只适合你:

BerkeleyDB

  

Berkeley DB旨在将数据存储为在一个可用访问方法中索引的键/值对中的不透明字节数据数组,如上所示。

伯克利非常强大,成熟且快速,但如果你想采用更轻量级的方法,那就使用SQLite

另一种选择是使用Google的LevelDB;它是用C ++编写的,但有Java wrappers around it。 LevelDB非常快速且非常轻量级!

如果没有关于项目的更多细节,我只能说:

  • 使用所有这些解决方案,键/值对将存储在同一个文件中(如果需要,多个实例可以存储到单独的文件中,但我不明白为什么会这样。)
  • BerkeleyDB和LevelDB具有非常好的缓存和映射功能。
  • BDB和LDB也允许压缩(不确定SQLite是否也这样做)。
  • 根据您的密钥分配(例如,如果您使用像Google CityHash这样的良好散列函数),您可以实现非常好的数据位置,从而减少表扫描。
  • 您应该编写自己的线程安全缓冲区,并且应避免让多个线程写入BDB / LDB,因为这些解决方案是基于磁盘的,并且您通常不需要多线程磁盘I / O操作。

批判:   - 我不确定你的意思是“键值缓冲区映射”......你是否将缓冲区映射到每个键?你为什么需要那个?

答案 2 :(得分:0)

您是否看过使用Hadoop

答案 3 :(得分:0)

Chronicle Map应该是解决此问题的好方法。

一般来说,无论是在操作速度还是消耗内存方面,它都是非常有效的。即它之前的建议比BerkeleyDB much faster

Chronicle Map是一个分段存储,允许并行处理段,例如: G:

for (int i = 0; i < chronicleMap.segments(); i++) {
  int segmentIndex = i;
  executor.submit(() -> {
    chronicleMap.segmentContext(segmentIndex).forEachSegmentEntry(entry -> {
      // do processing with entry.key() and entry.value(),
      // value() could be a List or some Iterator-like abstraction
    });
  });
}

请参阅MapSegmentContext Javadocs

但是,每个键could not always be handled efficiently with Chronicle Map具有(逻辑上)多个值。但在你的情况下,如果你只需要处理每个键的静态值集,而不是添加/删除值,它就可以正常工作。