为了使读者更加深刻地认识理解tomcat的相关组件概念,我将采用一种启发式的讲解方式来介绍tomcat的总体设计!从如何设计一个应用服务器开始,逐步完善、直至最终推导出tomcat的整体架构!
从最基本的功能来讲,可以简单地将服务器看成一个应用(这里不细分容器服务器、应用服务器等),其只需要能接受客户端的请求并进行解析,完成相关业务处理,最后将处理结果作为响应返回给客户端。
通常情况下,我们可通过socket监听服务器指定端口来实现该功能,按描述可设计如下:
我们通过start()方法启动服务器,打开socket链接,监听服务器端口,接受客户端的请求并返回响应。同时提供stop方法()来停止服务器并释放网络资源。
如果我们设计的不是一款服务器,仅仅是作为嵌入在应用系统中的一个远程请求处理方案且QPS要求不高的,这或许是个不错的方案!但是,我们设计的是应用服务器
在实践中很快就发现将请求和处理放到一起扩展性很差,如对于适配多种网络协议,但是请求处理逻辑是相同的。因为tomcat始终支持与apache集成,无论是AJP协议还是HTTP协议。例如,当web应用单独部署到tomcat时采用HTTP协议;当apache进行集群部署时,采用AJP协议进行连接。应用服务器tomcat在两者架构下切换时,应确保web应用不需做任何变更。
所以,很自然地可以想到将网络协议与请求处理从概念上进行分离,如下图:
一个server可以包括多个connector和container。其中connector负责开启socket并监听客户端请求、返回响应数据;container负责具体的请求处理;各自拥有自己的start()和stop()方法来加载和释放自己维护的资源。
同样,该设计仍然有缺点,就是如何知晓来自某个connector的请求由哪个container处理呢?更合理的设计应该如下:
或许你们想过可以通过维护一个复杂的映射规则来解决上述问题,但是事实是多次一举的,因为上述的设计已经足够灵活!如图2-3,一个server包含多个service,一个service负责多个connector和一个container,这样来自container的请求只能由它所属的service维护的container来处理。多个service是相互独立的,但共享一个JVM以及系统类库。
在tomcat规范中更多地把container组件名称为engine,用以表示整个servlet引擎,但需记住不是servlet容器,server才表示servlet容器。引擎只需要负责请求的处理,不用考虑请求的链接、协议、返回等的处理。
上一节解决了协议与容器的解耦,但是我们设计的是应用服务器,用来部署并运行web应用的,是一个运行环境,而非一个具体的业务处理系统。因此,我们需要在engine容器中支持web管理应用,当收到connector的处理请求时,engine能够找到一个合适的web应用来处理。
我们使用context来表示一个web应用,并且一个engine可以包含多个context。
此时,设计已经越来越完美了!但此时又有个实际的场景,线上主机承担着多个域名的服务,我们该如何处理?在该主机上运行多个应用服务器实例?可以!但如果只运行一个实例呢?
我们可以将每个域名视为一个虚拟的主机,在每个虚拟的主机包含多个web应用,客户端用户来说是不关心这些的,所以如下图:
一个host主机可包含多个context应用。
通过阅读servlet规范可知,在一个web应用中是通过包含多个servlet实例以处理不同的请求链接,所以还需要一个组件,该组件在tomcat被称为wrapper,所以修改后的设计如下图:
截止目前,我们多次提到“容器”这个概念,有时指engine,有时指context,但实际他代表着一类组件,这类组件的作用就是接受请求并返回响应数据。尽管具体操作可能委托给子组件完成,但从行为定义上是一致的。基于这个概念,我们可以再次修改设计图,如下:
我们使用container来表示容器,其可添加并维护子容器,因此engine、host、context、wrapper均继承container。service是组合着engine(在tomcat8.5.6之前是组合container)。
请记住,框架设计是否需要如此复杂是根据需求来看,如只需要嵌入式启动tomcat、不必支持多web应用,那么完全可以只在service中维护一个简化版的engine即可。
tomcat的container还有一个很重要的功能,就是后台处理,用于实现定期异步处理,如对文件变更扫描等,实现原理是在container定义了backgroundProcess()方法,并且其基础抽象类containerbase确保在启动组件的同时,异步启动后台处理。因此,在绝大多数情况下,各个容器组件只需要重写该方法即可,不必考虑创建异步线程。