4

我需要一个单一的写入器和多个读取器(最多 5 个)机制,写入器将每个大小几乎 1 MB 的数据和每秒 15 个包连续推送,这将在 c++ 中编写。我正在尝试做的是一个线程不断写入数据,而 5 个读取器将同时根据数据的时间戳进行一些搜索操作。我必须将每个数据包保留 60 分钟,然后才能将它们从容器中取出。

由于数据可以像 15 MB * 60 sec * 60 min = 54000MB/h 一样增长,我需要将近 50 GB 的空间来保存数据并使操作对写入器和读取器都足够快。但是问题是我们不能将这种大小的数据保存在缓存或 RAM 上,所以它必须放在像 SSD 这样的硬盘驱动器中(HDD 对于这种操作来说太慢了)

到目前为止,我一直在想的是,将循环缓冲区(因为我可以计算最大大小)直接实现到 SSD 上,到目前为止我找不到合适的例子,我不知道是否有可能或不可能,或者实现某种映射机制,一个循环数组将在 RAM 中可用,它只保留数据的时间戳和内存的物理地址,以搜索硬盘驱动器上可用的数据. 所以我猜至少搜索操作会更快。

由于任何类型的锁、互斥锁或信号量都会减慢操作速度(尤其是写入至关重要,我们不能因为任何读取操作而丢失数据)我不想使用它们。我知道有一些共享锁可用,但我再次认为它们有一些缺点。有没有办法/想法来实现这种无锁、无等待和线程安全的系统?任何数据结构(容器)、模式、示例代码/项目或其他类型的建议都将受到高度赞赏,谢谢...</p>

编辑:除了更大数量的 RAM,还有其他想法吗?

4

4 回答 4

3

这可以在商用 PC 上完成(并且可以扩展到服务器而无需更改代码)。

锁不是问题。如果只有一个编写者和很少的消费者在大数据上执行耗时的任务,那么您将很少遇到锁定并且几乎为零的锁定争用,因此这不是问题。
从简单的自旋锁(如果您真的非常渴望低延迟)或最好的pthread_mutex自旋锁(无论如何,它在大多数情况下都回退到自旋锁)中的任何东西都可以。没有什么花哨。

请注意,您不会获取锁,从套接字接收一兆字节的数据,将其写入磁盘,然后释放锁。这不是它的工作原理。
您收到一兆字节的数据并将其写入您独占的区域,然后获取锁,更改指针(并因此转移所有权)并释放锁。锁保护元数据,而不是千兆字节大小的缓冲区中的每个字节。长时间运行的任务,短的锁定时间,争用 = 零。

至于实际数据,写出15MiB/s绝对没有挑战,普通硬盘可以做到5-6倍,SSD轻松做到10-20倍。这也不是您甚至需要自己做的事情。这是你可以留给操作系统来管理的东西。

我会在磁盘和内存映射上创建一个 54.1GB 1文件(假设它是一个 64 位系统,在谈到多千兆字节内存服务器时这是一个合理的假设,这没问题)。操作系统负责其余的工作。您只需将数据写入用作循环缓冲区2的映射区域。
最近编写的内容将或多或少保证3驻留在 RAM 中,因此消费者可以访问它而不会出错。较旧的数据可能在 RAM 中,也可能不在 RAM 中,具体取决于您的服务器是否有足够的可用物理 RAM。

仍然可以访问较旧的数据,但速度可能会稍慢(如果没有足够的物理 RAM 来保持整个集合的驻留)。但是,它不会影响读取最近写入的数据的生产者或消费者(除非机器的规格非常低,以至于它甚至无法在 RAM 中保存 2-3 个 1MiB 块,但是您遇到了不同的问题! )。

你对如何处理数据不是很具体,除了会有 5 个消费者,所以我不会太深入这部分。您可能必须实现一个作业调度系统,或者您可以将每个传入的块分成 5 个较小的块,或者其他任何东西——这取决于您想要做什么。

在任何情况下,您需要考虑的是映射环形缓冲区中“有效”的数据区域(作为指针,或者更好地作为映射中的偏移量)和“未使用”的区域。
生产者是映射的所有者,它“允许”消费者在元数据中给定的范围内访问数据(一对开始/结束的偏移量)。只有生产者可以更改此元数据。访问此元数据的任何人(包括生产者)都需要获取锁。

甚至可以使用原子操作来做到这一点,但是看到你很少锁定,我什至不会打扰。使用锁很容易,而且您不会犯任何微妙的错误。

由于生产者知道消费者只会查看明确定义的范围内的数据,因此它可以在不锁定的情况下写入边界之外的区域(称为“emtpy”的区域)。之后只需要锁定即可更改边界。

当 54.1Gib > 54Gib 时,您可以在映射中写入一百个备用 1MiB 块。这可能比需要的要多得多(应该做 2 或 3 个),但多做一些也没有什么坏处。当您写入新块时(并将有效范围增加 1),还要调整“有效范围”的另一端。这样,线程将不再被允许访问旧块,但仍在该块中工作的线程可以完成其工作(数据仍然存在)。
如果对正确性有严格要求,如果处理一个块需要很长时间(在这种情况下超过 1 1/2 分钟),这可能会产生竞争条件。如果你想绝对确定,你需要另一个锁,在最坏的情况下可能会阻塞生产者。这是你绝对不想要的,但是在最坏的情况下阻止生产者是唯一在每个人为的情况下都是 100% 正确的事情,除非假设的计算机具有无限的内存。
鉴于这种情况,我认为这种理论上的竞赛是“允许的”事情。如果处理一个块真的需要这么长时间,并且有这么多数据稳定地进入,那么你手头的问题就会严重得多,所以实际上,这不是问题。

如果你的老板在未来的某个时候决定你应该保留超过 1 小时的积压,你可以扩大文件并重新映射,当“空”区域在旧缓冲区大小的末尾下一个时,只需扩展“已知”文件大小,并在生产者中调整您的 max_size 值。消费者线程甚至不需要知道。您当然可以创建另一个文件、复制数据、交换并同时阻止消费者,但我认为这是一个较差的解决方案。尺寸增加可能没有必要立即可见,但另一方面非常希望它是一个“不可见”的过程。
如果您将更多 RAM 放入计算机中,您的程序将“神奇地”使用它,而无需您进行任何更改。操作系统只会在 RAM 中保留更多页面。如果您添加其他几个消费者,它仍然可以正常工作。


1故意比您需要的更大,让有一些“额外”的 1MiB 块。

2最好madvise在覆盖区域之前让操作系统(如果您使用具有破坏性 DONT_NEED 提示的系统,例如 Linux)不再对内容感兴趣。但是如果你不这样做,它会以任何一种方式工作,只是效率稍低,因为操作系统可能会执行读-修改-写操作,而写操作就足够了。

3当然从来没有真正的保证,但无论如何都会如此。

于 2013-11-19T14:20:56.240 回答
0

54GB/小时 = 15MB/秒。这些天好的 SSD 可以写入 300+ MB/s。如果您在 RAM 中保留 1 小时,然后偶尔将较旧的数据刷新到磁盘,您应该能够处理超过 15MB/s 的 10 倍(前提是您的搜索算法足够快以跟上)。

关于线程之间的快速锁定机制,我建议查看 RCU - Read-Copy Update。Linux 内核目前正在使用它来实现非常高效的锁定。

于 2013-11-19T09:33:15.750 回答
0

周围有很多好的建议。我想为循环缓冲区实现添加一点,你可以看看Boost Circular Buffer

于 2013-11-19T14:31:42.830 回答
0

你有一些最低硬件要求吗?如今,54GB 内存是完全可能的(如今许多主板可能需要 4x16GB,这甚至还不是服务器硬件)。因此,如果您需要 SSD,您可能还需要大量 RAM 并按照您的建议使用内存中的循环缓冲区。

此外,如果数据中有足够的冗余,使用一些廉价的压缩算法(那些在 CPU 上很容易使用的算法,即某种“0级”压缩)可能是可行的。即您不存储原始数据,而是存储由读者解压缩的一些压缩格式(可能还有一些索引)。

于 2013-11-19T09:36:02.690 回答