用于存储数以千万计的对象以进行查询的高效方法,每秒可以插入大量插入?


15

这基本上是一个日志记录/计数应用程序,它在p2p聊天网络上统计数据包的数量并统计数据包的类型等。这相当于在5分钟内大约有4-6百万个数据包。并且因为我仅对此信息进行“快照”,所以我仅每5分钟删除5分钟之前的数据包。因此,此收藏集中的物品数量上限约为10到1200万。

因为我需要与不同的超级对等方建立300条连接,所以每个数据包都可能尝试插入至少300次(这可能就是为什么将此数据保存在内存中是唯一合理的选择)的可能。

目前,我一直在使用字典来存储此信息。但是由于我要存储的项目太多,所以我遇到了大对象堆的问题,并且随着时间的推移,内存使用量不断增长。

Dictionary<ulong, Packet>

public class Packet
{
    public ushort RequesterPort;
    public bool IsSearch;
    public string SearchText;
    public bool Flagged;
    public byte PacketType;
    public DateTime TimeStamp;
}

我尝试使用mysql,但是它无法跟上我需要插入的数据量(在检查以确保它不是重复的时候),而那是在使用事务时。

我尝试了mongodb,但是该CPU的使用非常疯狂,并且没有保持任何状态。

我的主要问题是每5分钟出现一次,因为我会删除所有早于5分钟的数据包,并对该数据进行“快照”。当我使用LINQ查询来计算包含某种数据包类型的数据包的数量时。我还对数据调用了一个distinct()查询,在该查询中,我从键值对的键中剥离了4个字节(IP地址),并将其与键值对的值中的requestingport值组合在一起,并使用该值来获得不同数量的所有数据包的对等体。

该应用程序当前的内存使用量徘徊在1.1GB左右,并且在调用快照时,它甚至可以使使用量翻倍。

现在,如果我有大量的ram,这将不是问题,但是目前运行此虚拟机的vm仅限于2GB的ram。

有什么简单的解决方案吗?


它占用大量内存,并且最重要的是,您正在使用虚拟机来运行应用程序,哇。无论如何,您是否探索过用于存储数据包的memcached。基本上,您可以在单独的计算机上运行memcached,并且应用程序可以在vm本身上继续运行。

正如您已经尝试过MySQL和MongoDB一样,似乎您的应用程序要求(如果您想做对的话)表明您仅需要更多的功能。如果您的应用程序对您很重要,请增强服务器。您可能还想重新访问“清除”代码。我确信您会找到一种更优化的处理方式,以确保不会使您的应用无法使用。
马特·贝克曼

4
您的剖析器告诉您什么?
杰森克2012年

您不会比本地堆更快地得到任何东西。我的建议是在清除后手动调用垃圾回收。
vartec'Mar

@vartec-事实上,与普遍的看法相反,手动调用垃圾收集器实际上并不能保证立即进行垃圾收集。GC可能会根据自己的gc算法将操作推迟到以后的时间。每5分钟调用一次,甚至可能会增加压力,而不是缓解压力。只是说
而已

Answers:


12

与其拥有一本词典,然后在该词典中搜索太旧的条目,不如说是一本词典。有10本字典。每隔30秒左右创建一个新的“当前”词典,并丢弃最旧的词典,而无需进行任何搜索。

接下来,当您丢弃最旧的字典时,请将所有旧对象放到FILO队列中以备后用,而不是使用“ new”创建新对象,而是从FILO队列中拉出一个旧对象并使用一种方法来重建旧对象对象(除非旧对象队列为空)。这样可以避免大量分配和大量垃圾回收开销。


1
按时间片分区!就是我要建议的。
詹姆斯·安德森

问题是,我将不得不查询过去五分钟之内制作的所有这些词典。由于有300个连接,因此同一数据包将至少每次到达一次。因此,为了不止一次处理同一数据包,我必须将它们至少保留5分钟。
2012年

1
通用结构的部分问题是它们没有针对特定目的进行定制。也许您应该在Packet结构中添加“ nextItemForHash”字段和“ nextItemForTimeBucket”字段,并实现自己的哈希表,然后停止使用Dictionary。这样,您可以快速找到所有过旧的数据包,并且仅在插入数据包时搜索一次(例如,也吃蛋糕)。它也有助于减少内存管理的开销(因为“字典”不会为字典管理分配/释放额外的数据结构)。
布伦丹2012年

@Josh最快的确定哈希表的方法是确定哈希表。按时间划分的哈希集将很快,并且您仍然不需要搜索以驱逐旧项目。如果您以前从未看过,则可以将其存储在字典中。
2016年


3

想到的第一个想法就是为什么要等待5分钟。您能否更频繁地进行快照,从而减少在5分钟边界处看到的大过载?

其次,LINQ非常适合简洁的代码,但实际上LINQ是“常规” C#上的语法糖,不能保证它将生成最佳代码。作为练习,您可以尝试不使用LINQ来重写热点,虽然可能不会提高性能,但是您将更清楚地了解自己在做什么,这将使分析工作变得更加容易。

要看的另一件事是数据结构。我不知道您如何处理数据,但是可以以任何方式简化存储的数据吗?您能否使用字符串或字节数组,然后根据需要从这些项目中提取相关部分?您能使用结构而不是类,甚至用stackalloc做一些邪恶的事情来预留内存并避免GC运行吗?


1
不要使用字符串/字节数组,而应使用BitArray之类的东西:msdn.microsoft.com/en-us/library/…以避免手动进行位旋转。否则,这是一个很好的答案,除了更好的算法,更多的硬件或更好的硬件之外,实际上没有其他简单的选择。
Ed James

1
五分钟的事情是由于这300个连接可能接收到相同的数据包。因此,我必须跟踪已经处理的内容,并且5分钟就是数据包完全传播到该特定网络上所有节点所花费的时间。
2012年

3

简单方法:尝试使用memcached

  • 它经过优化可运行此类任务。
  • 它可以在不那么忙碌的盒子上重复使用备用内存,而不仅仅是在专用盒子上。
  • 它具有内置的缓存过期机制,该机制是惰性的,因此不会打h。

缺点是它基于内存,并且没有任何持久性。如果实例关闭,数据将消失。如果需要持久性,请自己序列化数据。

更复杂的方法:尝试Redis

  • 它经过优化可运行此类任务。
  • 它具有内置的缓存过期机制
  • 它易于缩放/碎片。
  • 它具有持久性。

缺点是它稍微复杂一些。


1
可以将Memcached拆分到多台计算机上,以增加可用的ram数量。您可能需要第二台服务器将数据序列化到文件系统,这样,如果内存缓存盒出现故障,您就不会丢失任何东西。Memcache API的使用非常简单,并且可以使用任何语言工作,从而使您可以在不同的地方使用不同的堆栈。
Michael Shopsin 2014年

1

您不必为提到的查询存储所有软件包。例如-包裹类型计数器:

您需要两个数组:

int[] packageCounters = new int[NumberOfTotalTypes];
int[,] counterDifferencePerMinute = new int[6, NumberOfTotalTypes];

第一个数组跟踪不同类型的包数。第二个数组跟踪每分钟添加了多少个软件包,以便您知道每分钟间隔需要删除多少个软件包。我希望您能告诉我们第二个数组用作循环FIFO队列。

因此,对于每个软件包,将执行以下操作:

packageCounters[packageType] += 1;
counterDifferencePerMinute[current, packageType] += 1;
if (oneMinutePassed) {
  current = (current + 1) % 6;
  for (int i = 0; i < NumberOfTotalTypes; i++) {
    packageCounters[i] -= counterDifferencePerMinute[current, i];
    counterDifferencePerMinute[current, i] = 0;
}

任何时候,索引都可以立即检索包计数器,我们不会存储所有包。


必须存储我所做的数据的主要原因是,这300个连接可能接收到相同的数据包。因此,我需要将每个可见的数据包至少保留五分钟,以确保我不会多次处理/计数它们。字典键的ulong是用来做什么的。
2012年

1

(我知道这是一个老问题,但是我在寻找类似问题的解决方案时遇到了这个问题,其中第二代垃圾收集通行证暂停了该应用几秒钟,因此可以为处于类似情况的其他人录音。

为您的数据使用结构而不是类(但请记住,它具有传递副本语义将其视为值)。这消除了搜索gc必须进行每个标记遍历的一级。

使用数组(如果您知道要存储的数据大小)或使用列表-内部使用数组。如果您确实需要快速随机访问,请使用数组索引字典。这将使gc需要搜索另外几个级别(如果使用SortedDictionary,则要删除更多或更多个级别)。

根据您正在执行的操作,搜索结构列表的速度可能比字典查找(由于内存的本地化)要快-特定应用程序的配置文件。

struct&list的组合可显着减少内存使用量和垃圾收集器扫描的大小。


我最近有一个实验,它使用sqlite github.com/modma/PersistenceCollections
ModMa
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.