并行处理数千次下载的最佳方式

问题描述

我正在创建一个应用程序,我必须在其中使用 Java 下载数千张图像(每个约 1 MB)。

我在 REST 请求中获取相册 URL 列表,每个相册包含多个图像。

所以我的请求看起来像:

[
  "www.abc.xyz/album1","www.abc.xyz/album2","www.abc.xyz/album3","www.abc.xyz/album4","www.abc.xyz/album5"
]

假设每个相册有 1000 张图片,那么我需要并行下载 50000 张图片

现在我已经使用 parallelStream() 实现了它,但我觉得我可以进一步优化它。

有两个主要类 - AlbumDownloaderImageDownloader(Spring 组件)。

所以主应用程序在专辑列表上创建了一个 parallelStream()

albumData.parallelStream().forEach(ad -> albumDownloader.downloadAlbum(ad));

在 AlbumDownloader -> downloadAlbum() 方法中还有一个 parallelStream() 方法

List<Boolean> downloadStatus = albumData.getimageDownloadData().parallelStream().map(idd -> imageDownloader.downloadImage(idd)).collect(Collectors.toList());

我正在考虑将 CompletableFutureExecutorService 一起使用,但我不确定应该使用多大的池?

我应该为每个专辑创建一个单独的池吗?

ExecutorService executor = Executors.newFixedThreadPool(Math.min(albumData.getimageDownloadData().size(),1000));

这将创建 5 个不同的池,每个池包含 1000 个线程,这就像 5000 个线程,这可能会降低性能而不是提高性能

你能不能给我一些想法让它非常非常快?

顺便说一下,我正在使用 Apache Commons IO FileUtils 下载文件,并且我有一台有 12 个可用 cpu 内核的机器。

解决方法

假设每个相册有 1000 张图片,那么我需要并行下载 50000 张图片。

认为您的应用程序并行执行 50000 件事是错误的。您想要做的是优化您的吞吐量 - 您正在尝试在最短的时间内下载所有图像。

您应该尝试一个固定大小的线程池,然后调整池中线程的数量,直到优化您的吞吐量——也许从处理器数量的两倍开始。如果您的应用程序主要是在等待网络或服务器,那么也许您可以增加池中的线程数,但您不希望服务器过载,从而使其缓慢爬行,并且您不想使您的应用程序崩溃具有大量线程。

这将创建 5 个不同的池,每个池包含 1000 个线程,这就像 5000 个线程,这可能会降低性能而不是提高性能。

除非每个专辑都有不同的服务器,或者每个专辑的下载量不同的其他原因,否则我认为多个池没有意义。

,

使其“非常非常快”的唯一方法是与服务器建立“非常非常快”的网络连接;例如将您的客户端与您下载的服务器放在一起。

您的下载速度将受到许多潜在瓶颈的限制。其中包括:

  1. 服务器的性能;即它可以多快地组合数据发送给您并通过其网络接口推送它。

  2. 服务施加的每用户请求限制。

  3. 客户端和服务器之间网络路径的端到端性能。

  4. 您正在运行的机器在从网络移动数据并将其(我猜)放到本地磁盘上的性能。

瓶颈可能是其中任何一个,也可能是它们的组合。

向问题抛出数千个线程不太可能改善情况。事实上,如果有的话,它可能会使性能低于最佳状态。例如:

  • 它可能会阻塞您的网络链接,或者
  • 它可能会在您从中获取数据的服务器中触发反占用或反 DOS 防御。

更好的主意是使用带有小型有界工作池的 ExecutorService,并将下载作为任务提交到池中。 (并尝试在下载之间保持 HTTP/HTTPS 连接打开。)


我还建议您确保您拥有权限来做您正在做的事情。从事音乐出版业务的公司拥有优秀的律师。如果他们认为您违反了他们的条款和条件或窃取了他们的知识产权,他们可能会让您的生活变得不愉快1

1 - 比如阻止您的 IP 地址或向您的服务提供商发出删除请求。