Java项目:使HashMap(包括加载 – 存储)性能更好

我正在尝试为我们的服务器编写代码,我必须通过URL查找用户访问类型。

现在,在开始时,我们每天都会看到1亿个不同的URL。 现在,到那时它每天变成近6亿个不同的URL。

对于1亿,我们所做的是:

1)使用并行数组构建HashMap,其键是URL的一部分(表示为LONG),值是URL的其他部分(表示为INT) – 键可以有多个值。

2)然后搜索HashMap以查找访问的URL时间。

现在,随着HashTable变得越来越大,我们所做的就是:

1)构建两个/三个单独的HashTable,并加载和存储它(在通用文件系统上)以查找URL访问的次数。

现在,问题是,

1)虽然HashTable的性能相当不错,但是在加载/存储HashTable时代码需要更多时间(我们使用文件通道,加载/存储HashTable需要16-19秒 – 20000万条入口 – 加载因子为0.5)

我们要问的是:

1)有任何评论如何解决这个问题?

2)如何减少加载/存储时间(我之前问过但似乎文件通道是最好的方法)?

3)存储一个大的HashTable(超过内存)并重复缓存它将是一个很好的解决方案? 如果是这样,怎么做(至少一些指针)。 我们尝试使用

RandomAccessFile raf = new RandomAccessFile("array.dat", "rw"); IntBuffer map = raf.getChannel().map(FileChannel.MapMode.READ_WRITE, 0, 1 << 30).order(ByteOrder.nativeOrder()).asIntBuffer(); 

然而,比以前更糟糕的表现。

谢谢。

注意:

1)根据Stack Overflow之前的建议,我们使用一些像TokyoCabinet这样的NoSQL DB,但根据我们的经验,自定义HashTable比1亿个键值对提供更好的性能。

2)磁盘缓存的预读数据是不可能的,因为当系统启动时,我们的应用程序将开始工作,并在系统启动的第二天开始工作。

我们忘了提到的是:

1)由于我们的应用程序是项目的一部分并且应用于小型校园,因此我们假设访问的URL不超过8亿。 因此,您可以认为600/700数据值是固定的。

2)我们主要关心的是表现。

3)我们必须在本地运行我们的应用程序。

编辑:我们的hashmap代码可以在这里找到。

最好将表作为内存映射缓冲区进行访问。 这样,您可以简单地实现对文件的随机访问,而无需担心加载和存储,并将缓存留给操作系统。 我看到你当前的实现已经使用内存映射访问进行读写,但它仍然会将内容加载到java堆之间。 避免这种数据重复和复制! 将后备文件本身视为数据结构,并且仅在您需要时才访问它实际需要的部分。

在该文件中,如果您确实确定哈希冲突不是问题,那么哈希映射将起作用。 否则我会在那里找到一个B +树 ,其节点大小与你的硬盘页面大小相同。 这样,每个磁盘访问将产生比单个密钥更多的可用数据,从而导致更浅的树和更少的单个磁盘操作。

我猜其他人会实现这样的东西,但如果您更喜欢自己的哈希映射实现,您可能更喜欢编写自己的内存映射B +树。

整个方法对我来说听起来很荒谬。 我收集你真正想要的东西是一个简单的访问计数器每个不同的URL。 就其本质而言,这些数据经常被写入,但很少被阅读。

为此,我只需要一个数据库表,并为每次访问添加一个新条目(它也可以作为日志)。 当您需要弄清楚访问URL的频率时,可以使用表中的SELECT COUNT轻松完成此操作(根据您存储的URL条目的额外数据,您甚至可以执行约束计数,例如昨天访问的频率,上周等)。

这使得所有工作都得到了真正需要的结果。

顺便说一句,您也可以从Web服务器日志文件中检索访问计数,因此您可能不需要自己编写任何数据。 先看看这个。

您可以使用像JCS这样的缓存框架。 10亿个键值对应该不是问题。

http://commons.apache.org/jcs/

绝对尝试redis ,认为它击败了其他任何东西

您可以使用Berkeley DB ,它基本上是用C编写的键/值存储,以获得最佳性能。 这是一个Oracle产品(虽然开源),所以我会认真对待它。

如果你的应用程序必须在不使用任何外部计算能力的情况下在本地运行,那么就没有比直接内存访问更高效的解决方案:唯一可以提供更好性能的数据结构,然后HashMap是一个数组,其中每个元素的访问是O(1)。 但是,这需要事先知道您拥有多少项,每个元素具有唯一的寻址索引,并且还能够保留重要的相邻内存。

在所描述的数组适用于有限的情况之后,您拥有HashTable,但随着数据大小的增加,冲突和动态resize的成本会增加并使性能变差。

您可以参考java.util.HashMap javadoc,也可以参考Wikipedia http://en.wikipedia.org/wiki/Hash_table来了解以下内容:

  • 计算它有多贵?
  • 价值如何分配良好?
  • 您正在使用的负载因子是什么,即您将解决冲突的成本是多少?
  • 在您完全包含所有数据之前,您需要多久调整一次HashMap的大小?

如果在构建HashMap时性能下降,我实际上认为它是ConcurrentHashMap(如果你并行构建它必须是线程安全的),你可能想调查它发生的原因。

一个简单但容易的开始是用树形图替换你的HashMap,它的性能是它的大小的确定性函数,并比较两个性能。


如果在另一方面我误解了你的问题并且你有机会在多台计算机上扩展计算,你有很多有趣的解决方案在市场上,正如有人已经指出的那样,我将添加Cassandra。

这些解决方案通过在多个节点之间分配负载来实现性能改进,但是在每个节点内部使用众所周知的算法来快速有效地寻址。

不清楚问题和后续讨论,但您的查询的性质是什么? 你之间的情况非常不同
a)在每个工作日内处理所有约7亿个URL,或者
b)击中少数~7亿个URL。

那么:查询数与URL数之比是多少?

根据您的描述,听起来您可能正在加载/卸载代表arrays不同部分的不同文件…这表明随机查询,这表明(b)。

同样,我知道你已经认识到“内存中存在”是不可行的(即你已经在多个文件中打破了数组),因此最佳的磁盘访问算法似乎是下一个业务顺序,不是吗?

您是否尝试过每个查询一个简单的搜索(n * arrayElementSize)来在文件中进行偏移,并将几页读入内存(您是否知道每个键的最大值数?)。 你已经(计算好了)你的数组的基本索引,所以这应该很容易原型。

我建议你使用Oracle Coherence Cache 。 您可以获得HashTable所有好处,它具有Map所具有的所有方法。

性能方面,您可以根据您的要求存储数据。请看一下。

您可以尝试使用HugeCollections ,我认为它是为此目的而编写的

HugeCollections
图书馆支持数百万或数十亿条目的馆藏。

特别是HugeMap

在内存数据库中使用开源sqlite

如果我理解正确,您的数据结构就不那么大了

 [(32 + 64) * 600 million] bits ie a 53.644 MB structure in memory 

地图数据结构也会消耗一些空间。 我发现了很难的方式,特洛伊是最有效的内存数据结构之一。 我将使用TLongIntHashMap来存储长键和整数值。 它存储原始基元,以便绕过Long和Integer内存对象

看起来你有一个大多数只读数据集不适合内存,你需要快速的密钥查找。 我担心除了一些可能的权衡之外,这里没有银弹解决方案。

如果你无处不在地访问600M记录,无论你做什么你将受到磁盘随机访问速度的限制(不是顺序访问速度)。 使用FileChannel.map直接访问文件(不,不要在内存中读取文件的内容,只需在MappedByteBuffer上操作。您的操作系统将为您处理缓存)。 投资SSD似乎是花钱的好方法(或者只是购买更多的内存?)。

这是校园环境吧? 也许您可以在实验室中使用计算机来制作memcached / redis / etc. 簇? 也许你可以在非工作时间使用它?

如果您同时访问某些可识别的数据(即现在我们分析域a,然后b等),那么将数据拆分成桶是个好主意。 就像保持相关数据在物理上接近一样,以帮助缓存。 或者可能预先对url进行排序,并以二进制搜索方式访问它们?

如果碰撞的可能性是可接受的,可能不存储完整的URL但只有64位的ursh作为哈希键可以接受? 有些体操你可能会因为根本没有存放钥匙而逃脱?

这是我目前的想法。