简体   繁体   English

Java和队列:多线程I / O的饱和问题

[英]Java and queues: saturation issues with multithreaded I/O

This question relates to the latest version of Java. 这个问题与最新版本的Java有关。

30 producer threads push strings to an abstract queue. 30个生成器线程将字符串推送到抽象队列。 One writer thread pops from the same queue and writes the string to a file that resides on a 5400 rpm HDD RAID array. 一个编写器线程从同一队列弹出,并将该字符串写入驻留在5400 rpm HDD RAID阵列上的文件。 The data is pushed at a rate of roughly 111 MBps, and popped/written at a rate of roughly 80MBps. 数据以大约111 MBps的速率推送,并以大约80MBps的速率弹出/写入。 The program lives for 5600 seconds, enough for about 176 GB of data to accumulate in the queue. 该程序存在5600秒,足以使大约176 GB的数据在队列中累积。 On the other hand, I'm restricted to a total of 64GB of main memory. 另一方面,我限制为总共64GB的主内存。

My question is: What type of queue should I use? 我的问题是:我应该使用什么类型的队列?

Here's what I've tried so far. 这是我到目前为止所尝试的内容。

1) ArrayBlockingQueue . 1) ArrayBlockingQueue The problem with this bounded queue is that, regardless of the initial size of the array, I always end up with liveness issues as soon as it fills up. 这个有界队列的问题在于,无论数组的初始大小如何,一旦填满,我总是会遇到活动问题。 In fact, a few seconds after the program starts, top reports only a single active thread. 实际上,在程序启动几秒后, top只报告一个活动线程。 Profiling reveals that, on average, the producer threads spend most of their time waiting for the queue to free up. 分析表明,平均而言,生产者线程花费大部分时间等待队列释放。 This is regardless of whether or not I use the fair-access policy (with the second argument in the constructor set to true). 这与我是否使用公平访问策略(构造函数中的第二个参数设置为true)无关。

2) ConcurrentLinkedQueue . 2) ConcurrentLinkedQueue As far as liveness goes, this unbounded queue performs better. 就活力而言,这个无限制的队列表现得更好。 Until I run out of memory, about seven hundred seconds in, all thirty producer threads are active. 直到我耗尽内存,大约七百秒,所有三十个生产者线程都处于活动状态。 After I cross the 64GB limit, however, things become incredibly slow. 然而,在我超过64GB限制之后,事情变得异常缓慢。 I conjecture that this is because of paging issues, though I haven't performed any experiments to prove this. 我猜想这是因为分页问题,​​虽然我没有进行任何实验来证明这一点。

I foresee two ways out of my situation. 我预见到两种方式可以解决我的问题。

1) Buy an SSD. 1)购买SSD。 Hopefully the I/O rate increases will help. 希望I / O速率提高会有所帮助。

2) Compress the output stream before writing to file. 2)在写入文件之前压缩输出流。

Is there an alternative? 还有其他选择吗? Am I missing something in the way either of the above queues are constructed/used? 我是否遗漏了构造/使用上述队列的方式? Is there a cleverer way to use them? 是否有更聪明的方式来使用它们? The Java Concurrency in Practice book proposes a number of saturation policies (Section 8.3.3) in the case that bounded queues fill up faster than they can be exhausted, but unfortunately none of them---abort, caller runs, and the two discard policies---apply in my scenario. Java Concurrency in Practice一书提出了一些饱和策略(第8.3.3节),在有限队列填充速度快于耗尽的情况下,但不幸的是,没有一个 - 中止,调用者运行,以及两个丢弃政策---适用于我的方案。

Look for the bottleneck. 寻找瓶颈。 You produce more then you consume, a bounded queue makes absolutely sense, since you don't want to run out of memory. 你产生的消耗量更多,有界的队列绝对有意义,因为你不想耗尽内存。

Try to make your consumer faster. 尽量让您的消费者更快。 Profile and look where the most time is spent. 剖析并查看花费最多时间的位置。 Since you write to a disk here some thoughts: 既然你在这里写了一些想法:

  • Could you use NIO for your problem? 你能用NIO来解决问题吗? (maybe FileChannel#transferTo() ) (也许是FileChannel#transferTo()
  • Flush only when needed. 仅在需要时冲洗。
  • If you have enough CPU reserves, compress the stream? 如果你有足够的CPU储备,压缩流? (as you already mentioned) (正如你已经提到的那样)
  • optimize your disks for speed (raid cache, etc.) 优化磁盘以提高速度(raid缓存等)
  • faster disks 更快的磁盘

As @Flavio already said, for the producer-consumer pattern, i see no problem there and it should be the way it is now. 正如@Flavio已经说过的那样,对于生产者 - 消费者模式,我认为没有任何问题,它应该是现在的样子。 In the end the slowest party controls the speed. 最后,最慢的一方控制速度。

I can't see the problem here. 我在这里看不到问题。 In a producer-consumer situation, the system will always go with the speed of the slower party. 在生产者 - 消费者的情况下,系统将始终以较慢的一方的速度行事。 If the producer is faster than the consumer, it will be slowed down to the consumer speed when the queue fills up. 如果生产者比消费者更快,那么当队列填满时,它将减慢到消费者的速度。

If your constraint is that you can not slow down the producer, you will have to find a way to speed up the consumer. 如果你的约束是你不能减慢生产者的速度,你将不得不找到一种方法来加速消费者。 Profile the consumer (don't start too fancy, a few System.nanoTime() calls often give enough information), check where it spends most of its time, and start optimizing from there. 描述消费者(不要开始太花哨,一些System.nanoTime()调用经常提供足够的信息),检查它花费大部分时间的地方,并从那里开始优化。 If you have a CPU bottleneck you can improve your algorithm, add more threads, etc. If you have a disk bottleneck try writing less (compression is a good idea), get a faster disk, write on two disks instead of one... 如果您有CPU瓶颈,可以改进算法,添加更多线程等。如果您有磁盘瓶颈,请尝试少写(压缩是个好主意),获得更快的磁盘,写入两个磁盘而不是一个...

According to java " Queue implementation " there are other classes that should be right for you: 根据java“ 队列实现 ”,还有其他适合您的类:

  • LinkedBlockingQueue 的LinkedBlockingQueue
  • PriorityBlockingQueue 的PriorityBlockingQueue
  • DelayQueue DelayQueue
  • SynchronousQueue 的SynchronousQueue
  • LinkedTransferQueue LinkedTransferQueue
  • TransferQueue TransferQueue

I don't know the performance of these classes or the memory usage but you can try by your self. 我不知道这些类的性能或内存使用情况,但你可以尝试自己。

I hope that this helps you. 我希望这对你有所帮助。

Why do you have 30 producers. 为什么你有30个生产者。 Is that number fixed by the problem domain, or is it just a number you picked? 这个号码是由问题域修复的,还是仅仅是您选择的号码? If the latter, you should reduce the number of producers until they produce at total rate that is larger than the consumption by only a small amount, and use a blocking queue (as others have suggested). 如果是后者,你应该减少生产者的数量,直到他们生产的总费率只比消费量大一点,并使用阻塞队列(正如其他人所建议的那样)。 Then you will keep your consumer busy, which is the performance limiting part, while minimizing use of other resources (memory, threads). 然后,您将使您的消费者忙碌,这是性能限制部分,同时最小化其他资源(内存,线程)的使用。

you have only 2 ways out: make suppliers slower or consumer faster. 你只有两种方法:让供应商变慢或消费者更快。 Slower producers can be done in many ways, particullary, using bounded queues. 较慢的生产者可以通过多种方式完成,特别是使用有界队列。 To make consumer faster, try https://www.google.ru/search?q=java+memory-mapped+file . 要让消费者更快,请尝试https://www.google.ru/search?q=java+memory-mapped+file Look at https://github.com/peter-lawrey/Java-Chronicle . 请查看https://github.com/peter-lawrey/Java-Chronicle

Another way is to free writing thread from work of preparing write buffers from strings. 另一种方法是从编写字符串写缓冲区的工作中释放写线程。 Let the producer threads emit ready buffers, not strings. 让生产者线程发出就绪缓冲区,而不是字符串。 Use limited number of buffers, say, 2*threadnumber=60. 使用有限数量的缓冲区,比如2 * threadnumber = 60。 Allocate all buffers at the start and then reuse them. 在开始时分配所有缓冲区,然后重复使用它们。 Use a queue for empty buffers. 将队列用于空缓冲区。 Producing thread takes a buffer from that queue, fills it and puts into writing queue. 生成线程从该队列中获取缓冲区,填充它并放入写入队列。 Writing thread takes buffers from writing thread, writes to disk and puts into the empty buffers queue. 编写线程从写入线程,写入磁盘并放入空缓冲区队列中获取缓冲区。

Yet another approach is to use asynchronous I/O . 另一种方法是使用异步I / O. Producers initiate writing operation themselves, without special writing thread. 生产者自己开始编写操作,没有特殊的写作线程。 Completion handler returns used buffer into tthe empty buffers queue. 完成处理程序将使用过的缓冲区返回到空缓冲区队列中。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM