美文网首页
[Todo]Understanding Play thread

[Todo]Understanding Play thread

作者: zerolinke | 来源:发表于2017-03-23 15:47 被阅读115次

    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交流。

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

    • 使用REST/WebService API通过第三方的客户端,即没有使用Play的asynchronous WS API。
    • 一些仅仅提供同步API的消息传递技术去发送一些消息。
    • 当你直接打开一些files或是sockets。
    • 一些需要很长时间去执行的CPU密集型操作。

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

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

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

    • The Play WS API
    • Asynchronous database drivers such as ReactiveMongo
    • Sending/receiving messages to/from Akka actors

    Play`s thread pools

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

    • Netty boss/worker thread pools - 通过使用Netty的方式处理Netty IO。应用自身的代码不会由这个线程池的线程处理。
    • Play default thread pool - 这个是你在Play框架中所有的应用代码执行的线程池。它是一个Akka的dispatcher,在应用中通过ActorSystem使用它。通过配置Akka去配置它,下面的内容会讨论它。

    注意:在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。

    相关文章

      网友评论

          本文标题:[Todo]Understanding Play thread

          本文链接:https://www.haomeiwen.com/subject/phttottx.html