在 Java 中播放框架异步处理和阻塞 I/O

Posted

技术标签:

【中文标题】在 Java 中播放框架异步处理和阻塞 I/O【英文标题】:Play framework async processing and blocking I/O in Java 【发布时间】:2014-04-27 11:12:00 【问题描述】:

我的应用程序使用 Play 框架来处理 REST 请求。我需要在 http 请求处理程序中执行一些可能持久的阻塞 I/O 操作。同时,我想有效地处理一些短暂的请求。

如此处所述:

http://www.playframework.com/documentation/2.2.0/JavaAsync

可以异步运行持久的操作。另一方面,如此处所述:

http://www.playframework.com/documentation/2.2.x/ThreadPools

Play 框架使用相同的默认线程池,所有应用程序代码都在其中执行。至少在 Java api 中不可能在不同的线程池上运行异步工作。

所以,我的问题是,考虑到这样的操作无论如何都使用相同的线程池这一事实,是否值得异步运行可能阻塞的 I/O 操作。或者也许最好增加默认线程池大小,并且在这种情况下不要使用异步 api? (这样至少代码可读性会高很多)

【问题讨论】:

在单独的线程池上运行持久的操作,或者为每个操作启动单独的线程。 @AlexeiKaigorodov:我愿意,但我在 Java api 中没有发现这种可能性。不过,我在 Scala api 中读到了这种可能性。但我现在正在使用 Java。 你没有找到如何通过java API启动线程或线程池? :-) 不,我还没有找到如何使用本机 Play 框架 api 来做到这一点。我可以自己做 - 这是一种解决方案。但它需要有关 Play 本身的较低级别的知识,才能正确执行。并且可能与不同的 Play 助手不兼容,例如动作装饰器等 我不是 Play 专家,但我认为启动一个线程(池)不会有任何害处。但问题是,如何将长时间操作的结果传递回 Play 框架 - 应该使用一些异步机制,而不是在 Play 控制的线程上等待结果。 【参考方案1】:

我建议您设置自己的上下文并使用 Plays F.Promise<A> 在此处运行阻塞/cpu 密集型操作。与线程一样,最佳解决方案取决于许多因素,例如内核数量等。

首先在applications.conf中设置你的上下文:

play 
  akka 
    akka.loggers = ["akka.event.Logging$DefaultLogger", "akka.event.slf4j.Slf4jLogger"]
    loglevel = WARNING
    actor 
      default-dispatcher = 
        fork-join-executor 
          parallelism-min = 1
          parallelism-factor = 2
          parallelism-max = 6
        
      
      my-context 
        fork-join-executor 
          parallelism-min = 1
          parallelism-factor = 4
          parallelism-max = 16
        
      
    
  

然后在您的控制器中,使用 Plays Promises 使用您的上下文(我使用的是 Java 8):

public static F.Promise<Result> love() 
    ExecutionContext myExecutionContext = Akka.system().dispatchers().lookup("play.akka.actor.my-context");

    F.Promise<Integer> integerPromise = F.Promise.promise(() ->
            LongRunningProcess.run(10000L)
    , myExecutionContext);

    F.Promise<Integer> integerPromise2 = F.Promise.promise(() ->
            LongRunningProcess.run(10000L)
    , myExecutionContext);

    return integerPromise.flatMap(i -> integerPromise2.map(x -> ok()));

这样,您的 Play 应用仍将处理 default-dispatcher 执行上下文中的短期请求,而阻塞/cpu 密集型将在 my-context 中运行。

我制作了一个非常简短的示例供您演示,请在 github 上查看。

【讨论】:

好答案,我发现这也是正确的做法。我在默认上下文中运行时间/cpu 密集型进程,这导致其他 Play Futures 出现问题,即 Play Web Service API 不再工作。将我的密集流程转移到他们自己的上下文中解决了 WS 请求的问题。

以上是关于在 Java 中播放框架异步处理和阻塞 I/O的主要内容,如果未能解决你的问题,请参考以下文章

Netty之非阻塞处理

五种I/O模型和Java NIO源码分析

java网络通信:异步非阻塞I/O (NIO)

为啥说nodejs是异步非阻塞

聊聊阻塞与非阻塞同步与异步I/O模型

如何优雅的处理Nodejs中的异步回调