Java:使用异步编程优化应用程序

时间:2015-09-23 13:15:57

标签: java asynchronous optimization

我必须修改dropwizard应用程序以改善其运行时间。基本上,该应用程序每天接收大约300万个URL并下载并解析它们以检测恶意内容。问题是该应用程序只能处理100万个URL。当我查看应用程序时,我发现它正在进行大量的顺序调用。我想就如何通过异步或其他技术改进应用程序提出一些建议。

所需代码如下: -

orchestrator.detectContent(urlRequest)

我正在考虑以下方法: -

  • 我没有通过POST调用dropwizard资源方法,而是直接从调度程序调用{​​{1}}。

  • orchestrator可以返回detectionScore,我会将所有的detectScores存储在map / table中,并执行批量数据库插入,而不是像现在的代码那样单独插入。

我想对上述方法以及可能的其他技术进行一些评论,以便我可以改善运行时间。另外,我刚刚阅读了Java异步编程,但似乎无法理解如何在上面的代码中使用它,所以也希望对此有所帮助。

感谢。

编辑: 我可以想到两个瓶颈:

  • 下载网页
  • 将结果插入数据库(数据库位于另一个系统中)
  • 似乎一次处理1个URL

系统有8 GB内存,其中4 GB似乎是免费的

$ free -m
             total       used       free     shared    buffers     cached
Mem:          7843       4496       3346          0        193       2339
-/+ buffers/cache:       1964       5879 
Swap:         1952        489       1463 

CPU使用率也很小:

top - 13:31:19 up 19 days, 15:39,  3 users,  load average: 0.00, 0.00, 0.00
Tasks: 215 total,   1 running, 214 sleeping,   0 stopped,   0 zombie
Cpu(s):  0.5%us,  0.0%sy,  0.0%ni, 99.4%id,  0.1%wa,  0.0%hi,  0.0%si,  0.0%st
Mem:   8031412k total,  4605196k used,  3426216k free,   198040k buffers
Swap:  1999868k total,   501020k used,  1498848k free,  2395344k cached

2 个答案:

答案 0 :(得分:7)

首先检查你大部分时间都在哪里。

我认为大部分时间都没有下载网址了。

如果下载网址占用时间超过90%,可能无法改善您的应用程序,因为瓶颈不是java而是您的网络。

仅在下载时间属于网络功能

时才考虑以下事项

如果下载时间不是很高,您可以尝试提高性能。标准方法是使用生产者消费者链。有关详细信息,请参阅here

基本上你可以将你的工作分成如下:

Downloading --> Parsing --> Saving 

下载是一个生产者,解析是下载过程的消费者,生成器用于保存过程和保存是消费者。

每个步骤可以由不同数量的线程执行。例如,您可以拥有3个下载线程,5个解析线程和1个保存线程。

评论后编辑

假设瓶颈不是cpu时间,所以对java代码进行干预并不重要。

如果您知道每天下载的GigaBytes有多少可以查看它们是否接近您网络的最大带宽。

如果发生这种情况,有不同的可能性:

  • 使用Content-Encoding: gzip请求压缩内容(因此减少使用的带宽)
  • 在不同网络上工作的不同节点之间拆分应用程序(以便在不同网络之间划分带宽)
  • 更新您的带宽(因此请为您的网络增加带宽)
  • 请务必仅下载请求的内容(如果没有请求,请不要使用javascript,图片,css等)(以尽量减少带宽的使用)
  • 以前解决方案的组合

答案 1 :(得分:4)

受Davide(伟大的)答案的启发这里是一个使用simple-react(我写的I库)并行化的简单方法。请注意,它稍有不同,使用客户端来驱动服务器上的并发。

示例

LazyReact streamBuilder = new LazyReact(15,15);

streamBuilder.fromIterable(urlRequests)
      .filter(urlReq->!validateRequests.isWhitelisted(urlReq))
      .forEach(request -> {
           ContentDetectionClient.detectContent(request);
       });

<强>解释

看起来你可以从客户端驱动并发。这意味着您可以在服务器端跨线程分发工作,而无需额外的工作。在这个例子中,我们发出了15个并发请求,但您可以将其设置为接近服务器可以处理的最大值。您的应用程序是IO Bound,因此您可以使用大量线程来提高性能。

简单反应是一种期货流。所以我们在这里为ContentDetection客户端的每次调用创建一个Async任务。我们有15个线程可用,因此可以一次向服务器进行15次调用。

Java 7

Java 7的JDK 8功能的后端端口称为StreamSupport,您还可以通过RetroLambda向后端口传输Lambda表达式。

要使用CompletableFutures实现相同的解决方案,我们可以为每个符合条件的URL创建一个Future Task。 更新我认为我们不需要批量处理它们,我们可以使用Executor来限制活跃期货的数量。我们只需要在最后加入它们。

   Executor exec = Executors.newFixedThreadPool(maxActive);//15 threads
   List<CompletableFuture<Void>> futures= new ArrayList<>();

   for (UrlRequest request : urlRequests) {
            if (!validateRequests.isWhitelisted(request)) {
                futures.add(CompletableFuture.runAsync(()->ContentDetectionClient.detectContent(request), exec));
            }
        }
 CompletableFuture.allOf(futures.toArray())
                      .join();