tomcat架构分析(connector BIO 实现)
出处:http://gearever.iteye.com
在tomcat架构分析(概览)中已经介绍过,connector组件是service容器中的一部分。它主要是接收,解析http请求,然后调用本service下的相关servlet。由于tomcat从架构上采用的是一个分层结构,因此根据解析过的http请求,定位到相应的servlet也是一个相对比较复杂的过程。
整个connector实现了从接收socket到调用servlet的全部过程。先来看一下connector的功能逻辑;
- 接收socket
- 从socket获取数据包,并解析成HttpServletRequest对象
- 从engine容器开始走调用流程,经过各层valve,最后调用servlet完成业务逻辑
- 返回response,关闭socket
可以看出,整个connector组件是tomcat运行主干,之前介绍的各个模块都是tomcat启动时,静态创建好的,通过connector将这些模块串了起来。
通常在实际运行中,特别是对于一些互联网应用而言,网络吞吐一直是整个服务的瓶颈所在,因此,connector的运行效率在一定程度上影响了tomcat的整体性能。相对来说,tomcat在处理静态页面方面一直有一些瓶颈,因此通常的服务架构都是前端类似nginx的web服务器,后端挂上tomcat作为应用服务器(当然还有些其他原因,例如负载均衡等)。Tomcat在connector的优化上做了一些特殊的处理,这些都是可选的,通过部署,配置方便完成,例如APR(ApachePortableRuntime),BIO,NIO等。
目前connector支持的协议是HTTP和AJP。AJP是Apache与其他服务器之间的通信协议。通常在集群环境中,例如前端web服务器和后端应用服务器或servlet容器,使用AJP会比HTTP有更好的性能,这里引述apache官网上的一段话“Ifintegrationwiththenativewebserverisneededforanyreason,anAJPconnectorwillprovidefasterperformancethanproxiedHTTP.AJPclusteringisthemostefficientfromtheTomcatperspective.ItisotherwisefunctionallyequivalenttoHTTPclustering.”
本篇主要是针对HTTP协议的connector进行阐述。先来看一下connector的配置,在server.xml里;
<Connector port="80" URIEncoding="UTF-8" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="7443" />
熟悉的80端口不必说了。“protocol”这里是指这个connector支持的协议。针对HTTP协议而言,这个属性可以配置的值有:
- HTTP/1.1
- org.apache.coyote.http11.Http11Protocol–BIO实现
- org.apache.coyote.http11.Http11NioProtocol–NIO实现
- 定制的接口
配置“HTTP/1.1”和“org.apache.coyote.http11.Http11Protocol”的效果是一样的,因此connector的HTTP协议实现缺省是支持BIO的。无论是BIO还是NIO都是实现一个org.apache.coyote.ProtocolHandler接口,因此如果需要定制化,也必须实现这个接口。
本篇就来看看缺省状态下HTTPconnector的架构及其消息流。
可以看见connector中三大块
- Http11Protocol
- Mapper
- CoyoteAdapter
Http11Protocol
类全路径org.apache.coyote.http11.Http11Protocol,这是支持http的BIO实现。Http11Protocol包含了JIoEndpoint对象及Http11ConnectionHandler对象。
Http11ConnectionHandler对象维护了一个Http11Processor对象池,Http11Processor对象会调用CoyoteAdapter完成httprequest的解析和分派。
JIoEndpoint维护了两个线程池,Acceptor及Worker。Acceptor是接收socket,然后从Worker线程池中找出空闲的线程处理socket,如果worker线程池没有空闲线程,则Acceptor将阻塞。Worker是典型的线程池实现。Worker线程拿到socket后,就从Http11Processor对象池中获取Http11Processor对象,进一步处理。除了这个比较基础的Worker线程池,也可以通过基于javaconcurrent系列的java.util.concurrent.ThreadPoolExecutor线程池实现,不过需要在server.xml中配置相应的节点,即在connector同级别配置<Executor>,配置完后,使用ThreadPoolExecutor与Worker在实现上没有什么大的区别,就不赘述了。
<Executor name="tomcatThreadPool" namePrefix="catalina-exec-" maxThreads="150" minSpareThreads="4"/>
图中的箭头代表了消息流。
Mapper
类全路径org.apache.tomcat.util.http.mapper.Mapper,此对象维护了一个从Host到Wrapper的各级容器的快照。它主要是为了,当httprequest被解析后,能够将httprequest绑定到相应的servlet进行业务处理。前面的文章中已经说明,在加载各层容器时,会将它们注册到JMX中。
所以当connector组件启动的时候,会从JMX中查询出各层容器,然后再创建这个Mapper对象中的快照。
CoyoteAdapter
全路径org.apache.catalina.connector.CoyoteAdapter,此对象负责将httprequest解析成HttpServletRequest对象,之后绑定相应的容器,然后从engine开始逐层调用valve直至该servlet。在session管理中,已经说明,根据request中的jsessionid绑定服务器端的相应session。这个jsessionid按照优先级或是从requesturl中获取,或是从cookie中获取,然后再session池中找到相应匹配的session对象,然后将其封装到HttpServletRequest对象。所有这些都是在CoyoteAdapter中完成的。看一下将request解析为HttpServletRequest对象后,开始调用servlet的代码;
connector.getContainer().getPipeline().getFirst().invoke(request, response);
connector的容器就是StandardEngine,代码的可读性很强,获取StandardEngine的pipeline,然后从第一个valve开始调用逻辑,相应的过程请参照tomcat架构分析(valve机制)。