Play Framework:当请求超出可用线程时会发生什么

时间:2014-07-04 17:54:44

标签: multithreading scala playframework-2.0 threadpool

我在线程池服务阻塞请求中有一个线程。

  def sync = Action {
    import Contexts.blockingPool
    Future { 
        Thread.sleep(100)
    } 
    Ok("Done")
  }

在Contexts.blockingPool中配置为:

custom-pool {
    fork-join-executor {
            parallelism-min = 1
            parallelism-max = 1
    }
}

理论上,如果上述请求收到100个并发请求,则预期行为应为:1个请求应该休眠(100),其余99个请求应该被拒绝(或排队直到超时?)。但是我发现创建了额外的工作线程来为其余请求提供服务。我还观察到,由于池中的线程数小于收到的请求,因此延迟会增加(服务请求变慢)。

如果收到大于配置的线程池大小的请求,预期的行为是什么?

2 个答案:

答案 0 :(得分:19)

您的测试结构不正确无法检验您的假设。 如果你翻过this section in the docs,你会发现Play有一些线程池/执行上下文。对于您的问题而言重要的一个是默认线程池以及它与您的操作所服务的HTTP请求的关系。

正如文档所描述的那样,默认线程池是默认运行所有应用程序代码的地方。即所有动作代码,包括所有Future(未明确定义自己的执行上下文),都将在此执行上下文/线程池中运行。所以使用你的例子:

def sync = Action {

  // *** import Contexts.blockingPool
  // *** Future { 
  // *** Thread.sleep(100)
  // ***} 

  Ok("Done")
}

// ***评论的操作中的所有代码都将在默认线程池中运行。 即当请求被路由到您的操作时:

  1. Future Thread.sleep将被分派到您的自定义执行上下文
  2. 然后无需等待Future完成(因为它在其自己的线程池[Context.blockingPool]中运行,因此不会阻塞默认线程池上的任何线程)
  3. 评估您的Ok("Done")语句,客户端收到响应
  4. 约收到回复后100毫秒,您的Future完成
  5. 所以为了解释你的观察,当你同时发送100个请求时,Play会很乐意接受这些请求,路由到你的控制器动作(在默认线程池上执行),发送到你的{{1然后响应客户端。

    默认池的默认大小为

    Future

    每个核心使用1个线程,最多24个。 鉴于你的行动很少(不包括play { akka { ... actor { default-dispatcher = { fork-join-executor { parallelism-factor = 1.0 parallelism-max = 24 } } } } } ),你将能够毫不费力地处理1000 /秒的请求/秒。但是,您的Future需要花费更长的时间来处理待办事项,因为您阻止了自定义池中的唯一线程(Future)。

    如果您使用我稍微调整过的动作版本,您将在日志输出中看到确认上述说明的内容:

    blockingPool

    您的所有请求都会先得到快速处理,然后您的object Threading { def sync = Action { val defaultThreadPool = Thread.currentThread().getName; import Contexts.blockingPool Future { val blockingPool = Thread.currentThread().getName; Logger.debug(s"""\t>>> Done on thread: $blockingPool""") Thread.sleep(100) } Logger.debug(s"""Done on thread: $defaultThreadPool""") Results.Ok } } object Contexts { implicit val blockingPool: ExecutionContext = Akka.system.dispatchers.lookup("blocking-pool-context") } 会逐一完成。

    总而言之,如果你真的想测试Play如何处理许多同时请求,只有一个线程处理请求,那么你可以使用以下配置:

    Future

    您可能还想在此行为中添加play { akka { akka.loggers = ["akka.event.Logging$DefaultLogger", "akka.event.slf4j.Slf4jLogger"] loglevel = WARNING actor { default-dispatcher = { fork-join-executor { parallelism-min = 1 parallelism-max = 1 } } } } } (以减少默认线程池寂寞线程的速度)

    Thread.sleep

    现在,您的 ... Thread.sleep(100) Logger.debug(s"""<<< Done on thread: $defaultThreadPool""") Results.Ok } 将有1个请求线程和1个线程。 如果您使用高并发连接运行此操作,您会注意到,当Play逐个处理请求时,客户端会阻塞。这是你期望看到的......

答案 1 :(得分:3)

播放使用扩展AkkaForkJoinPool的{​​{1}}。 它有内部任务队列。 您可能还会发现此描述对于通过fork-join-pool处理阻塞代码很有意义:Scala: the global ExecutionContext makes your life easier