[Todo]Understanding Play thread

2017-03-23  本文已影响115人  zerolinke

Understanding Play thread pools

Play框架是一个自下而上的异步web框架。使用iteratees异步处理streams。Play使用比传统web框架线程更少的线程池,play-core的IO从来不会阻塞。

因此,如果你计划去写一个阻塞的IO代码,或是可能会做一个CPU密集型的工作,你需要确切的知道哪一个线程承受着很大的工作量,然后你需要做一些相应的调整。使用阻塞IO没有考虑到这可能会导致Play框架的性能非常低,比如,你可能会看到每秒只有少数的请求会被处理,而CPU的使用率只有5%。相比之下, 典型开发硬件 (如 macbook pro) 的基准显示, 在正确调整后, 每秒钟可以处理数以百计甚至上千个请求的工作负载。

Knowing when you are blocking

最常见的地方就是Play应用在与数据库的交流中会阻塞。不幸的是,没有任何主流的数据为JVM提供异步的数据库驱动。所以对于大多数的数据库,你只能选择使用阻塞的IO。值得注意的例外是ReactiveMongo,这是一个MongoDB的驱动使用Play的Iteratee库与MongoDB交流。

其他可能会阻塞你代码的场景包括:

通常来说如果你使用的API返回的是Futures,他就不会阻塞,而且它的就会阻塞。

注意:你看到这里可能会因此将你的阻塞代码包裹在Futures中。这不会使它就成为非阻塞的,这仅仅只会让这个阻塞发生在其他的线程中。你任然需要保证线程池有足够的线程数去处理阻塞。

相比之下,下面的IO类型是不会阻塞的:

Play`s thread pools

Play使用了一些数量的不同线程用于不同的目的:

注意:在Play 2.4之后这些线程池都被被合并到了Play default thread pool.

Using the default thread pool

Play框架中的所有actions都使用这个default thread pool.当做了某些异步操作,比如,调用map或是flatMap在future,你可能需要去提供一个明确的execution context用于执行这个function。一个execution context基本上是另一个名字ThreadPool.

在大多数情况下,适当的使用execution context是通过Play default thread pool,可以通过play.api.libs.concurrent.Execution.Implicits._,他可以通过importing到你的Scala source file:

import play.api.libs.concurrent.Execution.Implicits._

def someAsyncAction = Action.async {
  wsClient.url("http://www.playframework.com").get().map { response =>
    // This code block is executed in the imported default execution context
    // which happens to be the same thread pool in which the outer block of
    // code in this action will be executed.
    Results.Ok("The response code was " + response.status)
  }
}

Play的线程池直接连接到Application`s ActorSystem并且使用默认的dispatcher。

Configuring the default thread pool

这个默认的线程池可以被配置,使用标准的Akka配置在application.confakka命名空间下。这里有一个默认的Play的thread pool配置:

akka {
  actor {
    default-dispatcher {
      fork-join-executor {
        # Settings this to 1 instead of 3 seems to improve performance.
        parallelism-factor = 1.0

        # @richdougherty: Not sure why this is set below the Akka
        # default.
        parallelism-max = 24

        # Setting this to LIFO changes the fork-join-executor
        # to use a stack discipline for task scheduling. This usually
        # improves throughput at the cost of possibly increasing
        # latency and risking task starvation (which should be rare).
        task-peeking-mode = LIFO
      }
    }
  }
}

这个配置指明Akka创建1个线程/每个可用的处理器,线程池中的最大线程数为24.

当然你也可以尝试Akka的默认配置:

akka {
  actor {
    default-dispatcher {
      # This will be used if you have set "executor = "fork-join-executor""
      fork-join-executor {
        # Min number of threads to cap factor-based parallelism number to
        parallelism-min = 8

        # The parallelism factor is used to determine thread pool size using the
        # following formula: ceil(available processors * factor). Resulting size
        # is then bounded by the parallelism-min and parallelism-max values.
        parallelism-factor = 3.0

        # Max number of threads to cap factor-based parallelism number to
        parallelism-max = 64

        # Setting to "FIFO" to use queue like peeking mode which "poll" or "LIFO" to use stack
        # like peeking mode which "pop".
        task-peeking-mode = "FIFO"
      }
    }
  }
}

完整的配置选项你可以在这里看到here

Using other thread pools

在某些环境下,你可能希望调度work到其他的线程。比如像CPU密集运算,IO,或是数据库访问。你必须首先创建一个ThreadPool,在Scala中很简单:

val myExecutionContext: ExecutionContext = akkaSystem.dispatchers.lookup("my-context")

我们使用Akka去创建ExecutionContext,或者你可能想使用java的executors创建更容易的创建你自己的ExecutionContext,又或是Scala的fork join thread pool。去配置Akka的execution context,你可以添加下面的配置到你的application.conf:

my-context {
  fork-join-executor {
    parallelism-factor = 20.0
    parallelism-max = 200
  }
}

在Scala中使用execution context.你可能想要更简单的使用Future对象函数:


Future {
  // Some blocking or expensive code here
}(myExecutionContext)

或者你可以明确的使用它:

implicit val ec = myExecutionContext

Future {
  // Some blocking or expensive code here
}

Class loaders and thread locals

Class loaders和thread locals需要特殊的处理在多线程环境。

Application class loader

Play应用的thread context class loader可能并不能总是能够加载application classes。你可以明确的使用application class loader 去加载 classes.

Java

Class myClass = app.classloader().loadClass(myClassName);

Scala

val myClass = app.classloader.loadClass(myClassName)

显示的加载classes在运行Play为开发模式下比production模式要重要得多。这是因为Play的开发模式使用多个class loaders,以支持引用的自动重载。Some of Play’s threads might be bound to a class loader that only knows about a subset of your application’s classes.

在一些情况下你可能不能明确的使用application classloader。

上一篇 下一篇

猜你喜欢

热点阅读