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

dat*_*ser 6 multithreading scala threadpool playframework-2.0

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

  def sync = Action {
    import Contexts.blockingPool
    Future { 
        Thread.sleep(100)
    } 
    Ok("Done")
  }
Run Code Online (Sandbox Code Playgroud)

在Contexts.blockingPool中配置为:

custom-pool {
    fork-join-executor {
            parallelism-min = 1
            parallelism-max = 1
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

Don*_*ler 19

您的测试结构不正确,无法检验您的假设.如果您在文档中查看此部分,您将看到Play有一些线程池/执行上下文.对于您的问题而言重要的一个是默认线程池以及它与您的操作所服务的HTTP请求的关系.

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

def sync = Action {

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

  Ok("Done")
}
Run Code Online (Sandbox Code Playgroud)

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

  1. FutureThread.sleep将被分派到您的自定义执行上下文
  2. 然后无需等待它Future完成(因为它在它自己的线程池[ Context.blockingPool]中运行,因此不会阻塞默认线程池上的任何线程)
  3. 您的Ok("Done")语句将被评估,客户端将收到响应
  4. 约.收到回复后100毫秒,您就Future完成了

因此,为了向您解释观察,当您发送100个同时请求时,Play将很乐意接受这些请求,路由到您的控制器操作(在默认线程池上执行),发送给您Future,然后响应客户端.

默认池的默认大小为

play {
  akka {
    ...
    actor {
      default-dispatcher = {
        fork-join-executor {
          parallelism-factor = 1.0
          parallelism-max = 24
        }
      }
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

每个核心使用1个线程,最多24个.鉴于你的动作很少(不包括Future),你将能够毫不费力地处理1000个请求/秒.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")
}
Run Code Online (Sandbox Code Playgroud)

您的所有要求都会先得到快速处理,然后再Future逐一完成.

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

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
        }
      }
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

你可能还想像这样添加一个Thread.sleep你的动作(以减慢默认线程池的寂寞线程)

    ...
    Thread.sleep(100)
    Logger.debug(s"""<<< Done on thread: $defaultThreadPool""")
    Results.Ok
}
Run Code Online (Sandbox Code Playgroud)

现在你将有1个请求线程和1个线程Future.如果您使用高并发连接运行此操作,您会注意到,当Play逐个处理请求时,客户端会阻塞.这是你期望看到的......