转载请注明出处,保持署名 _ 作者:joymufeng_
1. 介绍
大家翘首以盼的Play2.1终于发布了,目前可用版本是Play 2.1-RC4。在此感谢Play!开发团队付出的辛勤努力!Play2.1以后版本中将会加入导出符合Servlet3.1规范的War包功能,相信大家对这个功能也很期待。废话少说,回归正题,先了解一下,Play2.1中究竟有哪些新的变化:
- Scala升级到2.10版本
与时俱进,这是Play!框架的一贯作风
- scala.concurrent.Future
这是scala 2.10中最激动人心的API,并发中的生产者/消费者问题,寥寥几行代码就搞定,真心钦佩!
- 模块化
模块化使得开发者可以定制自己的框架。比如你可以将Play框架定制成一个高性能的、异步Http Server。
- 允许Play项目模块化开发
- 异步执行的代码可以操作Http.Context
- 为Java开发者提供更好的线程模型
- 开发者可以控制控制器类的实例化,更容易和Spring框架集成
- 全新的Scala JSON API
- 全新的Filter API和CSRF保护
- RequireJS
- 改进内容协商
详细信息请参考:http://www.playframework.org/documentation/2.1-RC3/Highlights
Play2.1的架构图如下,核心组件仍然是Netty和AKKA,
线程是系统最宝贵的资源,也是影响系统性能最关键的因素。下面本文主要围绕“线程策略”这个主题,通过源码分析Play2.1的架构设计。
2. Netty线程策略
2.1 源码分析
Netty是一个异步的、基于事件驱动的高性能网络应用框架。非常适合编写Web服务器应用。在Play2.1中,Netty的入口类是play.core.server.NettyServer,该类中创建NioServerSocketChannelFactory代码如下:
new org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory(
Executors.newCachedThreadPool(NamedThreadFactory("netty-boss")),
Executors.newCachedThreadPool(NamedThreadFactory("netty-worker"))))
这里使用 Executors的newCachedThreadPool方法创建线程池,该方法的文档描述如下:
Creates a thread pool that creates new threads as needed, but will reuse previously constructed threads when they are available. These pools will typically improve the performance of programs that execute many short-lived asynchronous tasks. Calls to execute will reuse previously constructed threads if available. If no existing thread is available, a new thread will be created and added to the pool. Threads that have not been used for sixty seconds are terminated and removed from the cache. Thus, a pool that remains idle for long enough will not consume any resources. Note that pools with similar properties but different details (for example, timeout parameters) may be created usingThreadPoolExecutor constructors.
从文档描述中可以看出,newCachedThreadPool适合于大量短时间、异步任务的执行,在执行任务时,会重用之前创建的线程,如果没有可用的,则创建一个新的线程,并加入到线程池中。如果线程空闲超过60秒,则会被终止,并从线程池中移除。newCachedThreadPool的这些特性,刚好满足Netty的异步、事件驱动的需求。
2.2 Netty线程策略总结
Netty中处理ServerSocketChannel的"Boss threads", 和处理客户端Channel的"Worker threads"都使用newCachedThreadPool,即根据需要创建新的线程。"Boss threads"的数量取决于Netty绑定的端口个数,例如80和443端口;"Worker threads"的数量则取决于客户端请求数量。当客户端请求逐渐增多时,系统将变的不可控。
3. AKKA线程策略
AKKA中的actors实际执行的线程环境是由其关联的dispatcher管理的。所以这里我们只关注dispatcher的实现。 Play2.1中AKKA的线程策略和2.0版本相比,区别比较大。在play2.0中,actions、promises和websockets actors分别使用各自的dispatcher,其它actors使用默认的default-dispatcher。而在Play2.1中,所有的AKKA actors都使用默认的default-dispatcher,其配置如下:
default-dispatcher = {
fork-join-executor {
parallelism-min = 8
parallelism-factor = 1.0
parallelism-max = 24
}
}
从上面的配置可以看出,默认情况下,default-dispatcher只使用8个线程。
4. Play框架核心API的线程策略
为了保证快速处理客户端请求,Play中很多操作都是异步完成的。这些异步操作的执行环境是由play.core.Execution提供的。该类中创建了internalContext: scala.concurrent.ExecutionContext, 作为隐式的ExecutionContext, 线程池大小为processors个数,即CPU的总核数。
lazy val internalContext: scala.concurrent.ExecutionContext = {
val numberOfThreads = play.api.Play.maybeApplication.map(_.configuration.getInt("internal-threadpool-size")).flatten.getOrElse(Runtime.getRuntime.availableProcessors)
ExecutionContext.fromExecutorService(Executors.newFixedThreadPool(numberOfThreads, NamedThreadFactory("play-internal-execution-context")))
}
在Play2.1的核心API中,用到这个隐式参数的API大体如下: