Java"分层队列"实施快速生产者,减缓消费者

时间:2016-05-02 21:57:19

标签: java multithreading concurrency queue threadpool

我有一个生产者 - 消费者场景,生产者的生产速度比消费者消费的快得多。通常,解决方案是使生产者阻止,因为生产者/消费者场景的运行速度与最慢的组件一样快。限制或阻止生产者是一个很好的解决方案,因为我们的应用程序为消费者提供了足够的时间来赶上。

这里的图表描绘了一个完整的阶段"在我们的应用程序中,与更常见的情况相比:

      Our Application                 Common Scenario
 2N +--------+--------+
    |PPPPPPPP|oooooooo|                                         P = Producer
    |PPPPPPPP|oooooooo|                                         C = Consumer
  N +--------+--------+      N +--------+--------+--------+     o = Other Work
    |CPCPCPCP|CCCCCCCC|        |CPCPCPCP|CPCPCPCP|oooooooo|     N = number of tasks
    |CPCPCPCP|CCCCCCCC|        |CPCPCPCP|CPCPCPCP|oooooooo|
    -------------------        ----------------------------
    0       T/2       T        0       T/2       T      3T/2

这个想法是通过不抑制生产者来最大化吞吐量。

我们的任务运行的数据很容易被序列化,因此我计划实施一个文件系统解决方案,以便溢出所有无法立即满足的任务。

我使用Java ThreadPoolExecutorBlockingQueue最大容量,以确保我们不会耗尽内存。问题在于实施这样一个"分层"队列,其中可以在内存中排队的任务立即完成,否则数据将在磁盘上排队。

我提出了两种可能的解决方案:

  1. 使用BlockingQueueLinkedBlockingQueue实施作为参考,从头开始实施ArrayBlockingQueue。这可能就像在标准库中复制实现并添加文件系统读/写一样简单。
  2. 继续使用标准BlockingQueue实现,实现单独的FilesystemQueue来存储我的数据,并使用一个或多个线程将文件出列,创建Runnable并使用{将其排队{1}}。
  3. 这些是否合理,是否有更好的方法?

3 个答案:

答案 0 :(得分:4)

在寻求更复杂的解决方案之前,您是否真的相信使用有界BlockingQueue对您来说是一个交易破坏者?事实证明,增加堆大小并预先分配足够的容量仍然可以。它可以让您避免复杂性和性能不确定性,因为GC暂停的价格在您的舒适区内。

尽管如此,如果你的工作负载不平衡,它可以利用持久化大量内存不适合的消息(与经过验证的MPMC阻塞队列相比),听起来你需要一个更简单,更小版本的{{ 3}}或其ActiveMQ off-shoot。根据您的应用程序,您可能会发现ActiveMQ的其他功能非常有用,在这种情况下您可以直接使用它。如果没有,你可能会更好地搜索JMS空间,正如 bowmore 所暗示的那样。

答案 1 :(得分:2)

这听起来像是使用JMS队列而不是文件系统的理想情况。

不是使用阻塞队列,而是在永久JMS队列上发布消息。您仍然可以尝试分层方法,将JMS队列与BlockingQueue并行组合,在BlockingQueue已满时发布到JMS队列,但我确信纯JMS方法本身可以正常工作。

答案 2 :(得分:2)

第一个选项增加可用的堆空间大小 Dimitar建议的 Dimitrov ,使用记忆标志-Xmx,例如java -Xmx2048m

  

来自Oracle's Documentation:请注意,JVM使用的内存多于堆。例如   Java方法,线程堆栈和本机句柄在内存中分配   与堆分开,以及JVM内部数据结构。

这里还有一个java 堆内存如何分类的图表。

enter image description here

第二个选项是使用实现所请求功能的库。为此,您可以使用ashes-queue

  

来自项目概述:这是Java中的简单FIFO实现   持续支持。也就是说,如果队列已满,那么   溢出的消息将被保留,并在有可用时   插槽,它们将被放回内存中。

第三个选项创建您自己的实现。就此而言,您可以预览this thread,以指导您实现此目的。

您的建议包含在最后一个第三个选项中。两者都是合理的。从实现的角度来看,您应该使用第一个选项,因为它将保证更容易实现和清洁设计。