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