这可以在商用 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当然从来没有真正的保证,但无论如何都会如此。