关于java:如何确定高延迟网络请求的最佳线程数?

How to determine optimal number of threads for high latency network requests?

我正在写一个实用程序,它必须发出数千个网络请求。每个请求只接收一个响应中的小数据包(类似于ping),但可能需要几秒钟以上才能完成。处理每个响应在一行(简单)代码中完成。

这样做的净效果是,计算机不受IO限制、文件系统限制或CPU限制,它只受响应延迟的限制。

这是相似的,但不是同样的有一种方法来确定理想的线程数?和Java最佳的方法来确定线程的最佳数量[复制]…主要的区别是我只受延迟的限制。

我使用ExecutorService对象运行线程,使用Queue>跟踪需要检索结果的线程:

1
2
3
4
5
6
7
8
9
ExecutorService executorService = Executors.newFixedThreadPool(threadPoolSize);
Queue<Future<Integer>> futures = new LinkedList<Future<Integer>>();

for (int quad3 = 0 ; quad3 < 256 ; ++quad3) {
    for (int quad4 = 0 ; quad4 < 256 ; ++quad4) {
        byte[] quads = { quad1, quad2, (byte)quad3, (byte)quad4 };
        futures.add(executorService.submit(new RetrieverCallable(quads)));
    }
}

…然后,我将队列中的所有元素出列,并将结果放入所需的数据结构中:

1
2
3
4
5
6
7
8
int[] result = int[65536]
while(!futures.isEmpty()) {
    try {
        results[i] = futures.remove().get();
    } catch (Exception e) {
        addresses[i] = -1;
    }
}

我的第一个问题是:这是跟踪所有线程的合理方法吗?如果线程x需要一段时间才能完成,那么许多其他线程可能会在x完成之前完成。线程池是否会在等待打开的槽时耗尽自己,或者ExecutorService对象是否会以这样的方式管理池:将已完成但尚未处理的线程移出可用的槽,以便其他线程开始?

我的第二个问题是,我可以使用什么准则来找到进行这些调用的最佳线程数?我甚至不知道量级的指导。我知道它在256个线程上运行得很好,但在1024个线程上,它的总体时间似乎大致相同。CPU利用率徘徊在5%左右,所以这似乎不是问题。有了这么多线程,我应该查看哪些指标来比较不同的数字?显然,处理批处理的总时间,每个线程的平均时间…还有什么?记忆是问题吗?


它会让您震惊,但是您不需要任何线程来进行I/O(定量地说,这意味着0个线程)。您已经研究了多线程处理不会增加网络带宽,这是件好事。现在,是时候知道线程进行计算了。他们没有进行(高延迟)通信。通信由网络适配器执行,这是另一个进程,与CPU真正并行运行。分配一个线程(看看这个声称需要1个线程的先生们列出了分配的资源)只是为了睡眠,直到网络适配器完成它的工作是愚蠢的。I/O不需要线程=需要0个线程。

分配用于计算的线程与I/O请求并行是有意义的。线程的数量将取决于计算与通信的比率,并受CPU中核心数量的限制。

对不起,我不得不说,尽管你肯定暗示了阻止I/O的承诺,但是很多人不理解这一基本的事情。接受建议,使用异步I/O,您将看到问题不存在。


正如你提到的一个相关答案中提到的,布莱恩·戈茨在他的文章中已经很好地涵盖了这一点。

他似乎暗示,在您的情况下,建议您在提交线程计数之前收集度量。

Tuning the pool size

Tuning the size of a thread pool is largely a matter of avoiding two mistakes: having too few threads or too many threads. ...

The optimum size of a thread pool depends on the number of processors available and the nature of the tasks on the work queue. ...

For tasks that may wait for I/O to complete -- for example, a task that reads an HTTP request from a socket -- you will want to increase the pool size beyond the number of available processors, because not all threads will be working at all times. Using profiling, you can estimate the ratio of waiting time (WT) to service time (ST) for a typical request. If we call this ratio WT/ST, for an N-processor system, you'll want to have approximately N*(1+WT/ST) threads to keep the processors fully utilized.

我的重点。


你考虑过用演员吗?

Best practises.

  • Actors should be like nice co-workers: do their job efficiently
    without bothering everyone else needlessly and avoid hogging
    resources. Translated to programming this means to process events and
    generate responses (or more requests) in an event-driven manner.
    Actors should not block (i.e. passively wait while occupying a Thread)
    on some external entity—which might be a lock, a network socket,
    etc.—unless it is unavoidable; in the latter case see below.

对不起,我不能详细说明,因为这个用得不多。

更新

对于Akka来说,在好的用例中回答可能会有所帮助。斯卡拉:为什么演员很轻?


在所描述的情况下,线程的最佳数目是1。事实上,对于任何形式的"我应该使用多少线程"的疑问,这常常是令人惊讶的答案?

每个附加线程在堆栈(和相关联的GC根)、上下文切换和锁定方面都增加了额外的开销。这可能是可测量的,也可能不是可测量的:在所有的目标环境中有意义地测量它的效率是不平凡的。作为回报,提供任何好处的空间都很小,因为处理既不受CPU限制,也不受IO限制。

所以越少越好,即使只是为了降低风险。你不能少于1。


我假设所需的优化是处理所有请求的时间。你说请求的数量是"数千"。显然,最快的方法是一次发出所有请求,但这可能会溢出网络层。您应该确定网络层可以承载多少同时连接,并将这个数字作为程序的参数。

然后,为每个请求花费一个线程需要大量的内存。使用非阻塞套接字可以避免这种情况。在爪哇,有2种选择:NIO1与选择器,和异步通道的NIO2。NIO1很复杂,所以最好找到一个现成的库并重用它。NiO2很简单,但仅在JDK1.7之后才可用。

应在线程池上处理响应。我认为线程池中线程的数量不会对您的情况下的整体性能产生很大的影响。只需将线程池大小从1调到可用处理器的数量。


在我们的高性能系统中,我们使用@andrey chaschev描述的演员模型。

actor模型中的最佳线程数随CPU结构和每个框运行多少个进程(jvm)的不同而不同。我们的发现是

  • 如果您只有一个进程,请使用总CPU核心数-2。
  • 如果您有多个进程,请检查您的CPU结构。我们发现在一个CPU中拥有线程数量=核心数量是很好的——例如,如果您有一个4个CPU的服务器,每个服务器有4个核心,那么每个JVM使用4个线程可以获得最佳性能。在那之后,总是给你的操作系统留下至少一个核心。

  • 一个部分的答案,但我希望它有帮助。是的,内存可能是个问题:Java默认保留1 MB的线程堆栈(至少在Linux AMD64上)。因此,在您的箱中有几GB的RAM,这就将线程数限制为几千。

    你可以用一个像-XX:ThreadStackSize=64这样的标志来调优它。这将为您提供64kb,这在大多数情况下都是足够的。

    您还可以完全摆脱线程化,使用epoll来响应传入的响应。这是更可扩展的,但我没有实际的经验,这样做在Java中。