tomcat架构演化分析

Tomcat的架构总的来说是分层次的、可插拔的组件架构。分层次是指构成Tomcat的组件不是同一级别的,上层组件可以包含子组件,各个组件有其功能范围,当一个组件停止服务时,不会影响上层组件的服务。可插拔是指对于组件的添加和删除并不影响服务器的运行。那么为了达到可插拔的组件架构,分层次的组件架构必成为基础。

对于任何服务器,即使最简单的实现,从面向对象设计(OOD)的角度来说,我们都有必要将“服务器”这个概念抽象出来,为什么呢?因为只有有了这个概念,才能谈服务器的实例,服务器的功能等等其它概念,此之谓“皮之不存,毛将焉附”。

因此Tomcat将“服务器”抽象为Java接口org.apache.catalina.Server,显然Server应该就是最最顶层的组件了。有了Server这个抽象,很自然的,我们希望它能够提供对servlet和jsp支持的功能。但是我们发现这个概念太大了,我们还需再细化。所以别急,我们还有一些事情要解决。服务器要提供服务就必须能够启动,当然也应该能够停止吧,也就是说服务器应该是有生命的,在启动时初始化必要的资源,而在停止时将其其销毁掉。好吧,我们把这个也抽象出来,叫做生命周期接口,tomcat 实现为org.apache.catalina.Lifecycle.如上所述我们知道Lifecycle需要完成的工作了。

public void start() throws LifecycleException;
public void stop() throws LifecycleException;

接下来我们分析服务器如何来处理客户端的请求,一般的我们会在浏览器中输入如下格式的请求,http://192.168.8.221:8080/explorer/loginInit.do。对于服务器来说,要想处理这个请求,就必须监听指定的端口8080,当有TCP的请求包来时,建立Socket连接,分析并解析之,然后给客户端返回响应。在这个过程中,我们发现,其实包含了俩个功能点,即监听并接受请求处理请求。那么我们能否将这俩个功能给抽象出来呢?Tomcat告诉我们,可以。Tomcat将“监听并接收请求”抽象为org.apache.catalina.connector.Connector类,负责接受请求;将“处理请求”抽象为“容器”org.apache.catalina.Container,负责处理Connector传递过来的请求。Ok,到此,我们分析构建的简单服务器模型出来了,Server由Connector组件和Container组件结合提供web服务。如下图:


有了这个模型后,要实现一个简单的Server已经很简单了,但是在实现Container时,我们还是要做很多事情,如当来请求,我们怎么知道该请求对应的虚拟主机,以及请求的哪个应用,应该交给个servlet对象来处理?这样看来,Container还是太大了,需要细化。根据Servlet规范,我们知道,servlet属于某个应用,且有上下文环境,Container要根据应用上下文环境初始化servlet,然后根据servlet映射调用servlet的service方法。在这里“应用上下文环境”的概念很重要,Tomcat将其抽象为org.apache.catalina.Context,Context继承了Container接口。对于虚拟主机,Tomcat将其抽象为org.apache.catalina.Host,Host继承了Container接口。好了,有了这些概念,我们再回顾一下请求的处理过程:浏览器发出请求,Connector接受请求,将请求交由Container处理,Container查找请求对应的Host并将请求传递给它,Host拿到请求后查找相应的应用上下文环境,准备servlet环境并调用service方法。
现在,我们的服务器模型变成了如图所示了:


但是在Tomcat的实现体系中还有一个Engine的接口,Engine也继承了Container接口,那么这个接口有什么用呢?设计Engine有俩个目的:一,当希望使用拦截器查看(过滤或预处理)每个请求时,Engine是个很好的拦截点;二,当希望多个虚拟Host共享一个Http的Connector时,Engine是个很好的门面。所以,Engine接口是作为顶级Container组件来设计的,其作用相当于一个Container的门面。有了Engine,请求的处理过程变为:浏览器发出请求,Connector接受请求,将请求交由Container(这里是Engine)处理,Container(Engine来担当)查找请求对应的Host并将请求传递给它,Host拿到请求后查找相应的应用上下文环境,准备servlet环境并调用service方法。再看看服务器的模型,如图:


到目前,我们碰到的组件类型有Connector和Container,其实,这也就是Tomcat的核心组件。如上图,一组Connector和一个Container有机的组合在一起构成Server,就可以提供服务了,对于Tomcat来说,主要是提供Servlet服务,那么也就是说Tomcat服务器也可以提供其它服务了?是的,Tomcat将“一组Connector和一个Container有机的组合抽象为“服务”接口(org.apache.catalina.Service)。然而,这些服务实例彼此独立,仅仅共享JVM的基础设施,如系统类路径。进一步的,我们得到了服务器的框架模型,如图:


由上图,我们知道,对于Tomcat服务器来说,除了Server代表它自己以外,其它组件都是功能组件,都有其职责范围。Service为最顶层的组件,可以添加Connector和Container组件。Engine是Container的最顶层组件,可以添加Host组件,但不能添加父组件。Host组件的父组件是Engine,Host下面包含有Context组件。接下来看看标准的Tomcat体系结构,如图:



下面来分析下:

1. conf\server.xml元素结构

<Server port="8005" shutdown="SHUTDOWN">

        <Listener className="org.apache.catalina.mbeans.ServerLifecycleListener"/>

        <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener"/>

        <Service name="Catalina">

                //1、端口。将port=80,进入本地服务时就不用敲80在末尾了(浏览器默认),这样当安装了IIS服务(80端口)时,tomcat无法启动(它是serversocket)。

                <Connector URIEncoding="utf-8" connectionTimeout="20000" port="8080" protocol="org.apache.coyote.http11.Http11NioProtocol" redirectPort="8443" useBodyEncodingForURI="true"/>

                <Engine defaultHost="localhost" name="Catalina">

                        <Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true" xmlValidation="false" xmlNamespaceAware="false">

                                <Context docBase="mypushlet" path="/mypushlet" reloadable="true" source="org.eclipse.jst.jee.server:mypushlet"/>

                        </Host>

                </Engine>

        </Service>

</Server>


        1.1 - Server 

        A Server element represents the entire Catalina servlet container. (Singleton) 

        1.2 - Service 

        A Service element represents the combination of one or more Connector components that share a single Engine 

        Service是这样一个集合:它由一个或者多个Connector组成,以及一个Engine,负责处理所有Connector所获得的客户请求 

        1.3 - Connector 

        一个Connector将在某个指定端口上侦听客户请求,并将获得的请求交给Engine来处理,从Engine处获得回应并返回客户。 

        Coyote Http/1.1 Connector 在端口8080处侦听来自客户browser的http请求 

        Coyote JK2 Connector 在端口8009处侦听来自其它WebServer(Apache)的servlet/jsp代理请求 

        1.4 - Engine 

        An Engine is a Container that represents the entire Catalina servlet engine. 

        If used, an Engine is always the top level Container in a Catalina.

        Engine下可以配置多个虚拟主机Virtual Host,每个虚拟主机都有一个域名。 

        当Engine获得一个请求时,它把该请求匹配到某个Host上,然后把该请求交给该Host来处理 

        1.5 - Host  


2. 架构:

        Server: 其实就是BackGroud程序, 在Tomcat里面的Server的用处是启动和监听服务端事件(诸如重启、关闭等命令。SHUTDOWN)

        Service: 一类问题的解决方案。默认使用Tomcat-Standalone 模式的service。既给我们提供解析jsp和servlet的服务, 同时也提供给我们解析静态文本的服务。

        Connector:从socket传递过来的数据, 封装成Request, 传递给容器来处理。http、https、ajp(apache与tomcat)三种。

        Container: 当http connector把需求传递给顶级的container: Engin的时候, 我们的视线就应该移动到Container这个层面来了。

              在Container这个层, 我们包含了3种容器: Engin, Host, Context.

                Engin: 收到service传递过来的需求, 处理后, 将结果返回给service( service 是通过 connector 这个媒介来和Engin互动的 ).

                Host: Engin收到service传递过来的需求后,不会自己处理, 而是交给合适的Host来处理。

                Host在这里就是虚拟主机的意思, 通常我们都只会使用一个主机,既“localhost”本地机来处理。 

                Context: 一个web app,Host接到了从Host传过来的需求后, 也不会自己处理, 而是交给合适的Context来处理。 

        Compenent: 容器有各种各样的组件, 提供各种各样的增值服务。

                manager: 当一个容器里面装了manager组件后,这个容器就支持session管理了, 事实上在tomcat里面的session管理, 就是靠的在context里面装的manager component。

                logger: 当一个容器里面装了logger组件后,记录请求的日志。

                loader: 通常只会给我们的context容器使用, loader是用来启动context以及管理这个context的classloader用的。

                pipline: 容器间传递并过滤。


3. Tomcat的启动流程: 第一步是装配工作(父容器装上子容器,容器安插进组件)。 第二步是启动工作。 

3.1 Catalina.java

        1. 使用Digester技术装配tomcat服务器的各个容器(提供核心服务)与组件(提供增值服务)。

                1.1 装配工作的主要内容是安装各个大件。 比如:server下有什么样的servcie。 servcie下包含的Connector和Container等。 

                1.2 同时在装配工作这一步, 还完成了mbeans的配置工作。 在这里,我简单地但不十分精确地描述一下mbean是什么,干什么用的。

                我们自己生成的对象, 自己管理, 天经地义! 但是如果我们创建了对象了, 想让别人来管, 怎么办呢? 我想至少得告诉别人我们都有什么, 以及通过什么方法可以找到吧! JMX技术给我们提供了一种手段。 JMX里面主要有3种东西。Mbean, agent, connector.

                Mbean: 用来映射我们的对象。也许mbean就是我们创建的对象, 也许不是, 但有了它, 就可以引用到我们的对象了。

                Agent: 通过它, 就可以找到mbean了。

                Connector: 连接Agent的方式。 可以是http的, 也可以是rmi的,还可以直接通过socket。

                发生在tomcat 装配过程中的事情: GlobalResourcesLifecycleListener 类的初始化会被触发:

                protected static Registry registry = MBeanUtils.createRegistry(); 会运行

                MBeanUtils.createRegistry() 会依据/org/apache/catalina/mbeans/mbeans-descriptors.xml这个配置文件创建mbeans。 Ok, 外界就有了条途径访问tomcat中的各个组件了。

        2. 为top level 的server 做初始化工作。 实际上就是做通常会配置给service的两条connector.(http, ajp)

        3. 从server这个容器开始启动, 点燃整个tomcat.

        4. 为server做一个hook程序, 检测当server shutdown的时候, 关闭tomcat的各个容器用。

        5. 监听8005端口, 如果发送"SHUTDOWN"(默认培植下字符串)过来, 关闭8005serverSocket。

2.4 启动各个容器

        1. Server

                触发Server容器启动前(before_start), 启动中(start), 启动后(after_start)3个事件, 并运行相应的事件处理器。

                启动Server的子容器:Servcie. 

        2. Service

                启动Service的子容器:Engin

                启动Connector

        3. Engin

                到了Engin这个层次,以及以下级别的容器, Tomcat就使用了比较一致的启动方式了。

                首先, 运行各个容器自己特有一些任务

                随后, 触发启动前事件

                立即, 设置标签,就表示该容器已经启动

                接着, 启动容器中的各个组件: loader, logger, manager等等

                再接着,启动mapping组件。(注1)

                紧跟着,启动子容器。

                接下来,启动该容器的管道(pipline)

                然后, 触发启动中事件

                最后, 触发启动后事件。

                Engin大致会这么做, Host大致也会这么做, Context大致还是会这么做。 那么很显然地, 我们需要在这里使用到代码复用的技术。 tomcat在处理这个问题的时候, 漂亮地使用了抽象类来处理。 ContainerBase. 最后使得这部分完成复杂功能的代码显得干净利落, 干练爽快, 实在是令人觉得叹为观止, 细细品来, 直觉如享佳珍, 另人齿颊留香, 留恋往返啊!

                Engin的触发启动前事件里, 会激活绑定在Engin上的唯一一个Listener:EnginConfig。

                这个EnginConfig类基本上没有做什么事情, 就是把EnginConfig的调试级别设置为和Engin相当。 另外就是输出几行文本, 表示Engin已经配置完毕, 并没有做什么实质性的工作。

                注1: mapping组件的用处是, 当一个需求将要从父容器传递到子容器的时候, 而父容器又有多个子容器的话, 那么应该选择哪个子容器来处理需求呢? 这个由mapping 组件来定夺。

        4. Host

                同Engin一样, 也是调用ContainerBase里面的start()方法, 不过之前做了些自个儿的任务,就是往Host这个容器的通道(pipline)里面, 安装了一个叫做

                “org.apache.catalina.valves.ErrorReportValve”的阀门。

                这个阀门的用处是这样的: 需求在被Engin传递给Host后, 会继续传递给Context做具体的处理。 这里需求其实就是作为参数传递的Request, Response。 所以在context把需求处理完后, 通常会改动response。 而这个org.apache.catalina.valves.ErrorReportValve的作用就是检察response是否包含错误, 如果有就做相应的处理。

        5. Context

                到了这里, 就终于轮到了tomcat启动中真正的重头戏,启动Context了。

                StandardContext.start() 这个启动Context容器的方法被StandardHost调用.

                5.1 webappResources 该context所指向的具体目录

                5.2 安装defaultContex, DefaultContext 就是默认Context。 如果我们在一个Host下面安装了DefaultContext,而且defaultContext里面又安装了一个数据库连接池资源的话。 那么其他所有的在该Host下的Context, 都可以直接使用这个数据库连接池, 而不用格外做配置了。

                5.3 指定Loader. 通常用默认的org.apache.catalina.loader.WebappLoader这个类。   Loader就是用来指定这个context会用到哪些类啊, 哪些jar包啊这些什么的。

                5.4 指定 Manager. 通常使用默认的org.apache.catalina.session. StandardManager 。 Manager是用来管理session的。

                其实session的管理也很好实现。 以一种简单的session管理为例。 当需求传递过来的时候, 在Request对象里面有一个sessionId 属性。 OK, 得到这个sessionId后, 我们就可以把它作为map的key,而value我们可以放置一个HashMap. HashMap里边儿, 再放我们想放的东西。

                5.5 postWorkDirectory (). Tomcat下面有一个work目录。 我们把临时文件都扔在那儿去。 这个步骤就是在那里创建一个目录。 一般说来会在%CATALINA_HOME%/work/Standalone\localhost\ 这个地方生成一个目录。

                5.6 Binding thread。到了这里, 就应该发生 class Loader 互换了。 之前是看得见tomcat下面所有的class和lib. 接下来需要看得见当前context下的class。 所以要设置contextClassLoader, 同时还要把旧的ClassLoader记录下来,因为以后还要用的。

                5.7 启动 Loader. 指定这个Context具体要使用哪些classes, 用到哪些jar文件。 如果reloadable设置成了true, 就会启动一个线程来监视classes的变化, 如果有变化就重新启动Context。

                5.8 启动logger

                5.9 触发安装在它身上的一个监听器。

                lifecycle.fireLifecycleEvent(START_EVENT, null); 

                作为监听器之一,ContextConfig会被启动. ContextConfig就是用来配置web.xml的。 比如这个Context有多少Servlet, 又有多少Filter, 就是在这里给Context装上去的。

                5.9.1 defaultConfig. 每个context都得配置 tomcat/conf/web.xml 这个文件。

                5.9.2 applicationConfig 配置自己的 WEB-INF/web.xml 文件

                5.9.3 validateSecurityRoles 权限验证。 通常我们在访问/admin 或者/manager的时候,需要用户要么是admin的要么是manager的, 才能访问。 而且我们还可以限制那些资源可以访问, 而哪些不能。 都是在这里实现的。

                5.9.4 tldScan: 扫描一下, 需要用到哪些标签(tag lab)

                5.10 启动 manager

                5.11 postWelcomeFiles() 我们通常会用到的3个启动文件的名称:

                index.html、index.htm、index.jsp 就被默认地绑在了这个context上

                5.12 listenerStart 配置listener

                5.13 filterStart 配置 filter

                5.14 启动带有<load-on-startup>1</load-on-startup>的Servlet.

                顺序是从小到大: 1,2,3… 最后是0

                默认情况下, 至少会启动如下3个的Servlet: 

                org.apache.catalina.servlets.DefaultServlet   

                处理静态资源的Servlet. 什么图片啊, html啊, css啊, js啊都找他

                org.apache.catalina.servlets.InvokerServlet

                处理没有做Servlet Mapping的那些Servlet.

                org.apache.jasper.servlet.JspServlet 

                处理JSP文件的.

                5.15 标识context已经启动完毕,tomcat启动完毕。

 

        每个虚拟主机下都可以部署(deploy)一个或者多个Web App,每个Web App对应于一个Context,有一个Context path。 

        当Host获得一个请求时,将把该请求匹配到某个Context上,然后把该请求交给该Context来处理。

 

        1.6 - Context 

        一个Context对应于一个Web Application,一个Web Application由一个或者多个Servlet组成。

        Context在创建的时候将根据配置文件$CATALINA_HOME/conf/web.xml和$WEBAPP_HOME/WEB-INF/web.xml载入Servlet类。

        当Context获得请求时,将在自己的映射表(mapping table)中寻找相匹配的Servlet类,如果找到,则执行该类,获得请求的回应,并返回。 

 

        1.7 - 示例

        browser发送请求到本机端口8080--> Connector --> Engine --> Host --> Context --> servlet 构造request、response --> Context --> Host --> Engine -- > connector --> browser

 

 tomcat位什么使用Class.newInstance()创建类的实例而不是关键字new创建类的实例?

从JVM的角度看,我们使用关键字new创建一个类的时候,这个类可以没有被加载。但是使用Class对象的newInstance()方法的时候,就必须保证:

1、这个类已经加载;

2、这个类已经连接了。而完成上面两个步骤的正是Class的静态方法forName()所完成的,这个静态方法调用了启动类加载器,即加载 Java API的那个加载器。 
现在可以看出,Class对象的newInstance()(这种用法和Java中的工厂模式有着异曲同工之妙)实际上是把new这个方式分解为两步,即首先调用Class加载方法加载某个类,然后实例化。这样分步的好处是显而易见的。我们可以在调用class的静态加载方法forName时获得更好的灵活性,提供给了一种降耦的手段。 

Class.forName().newInstance()和通过new得到对象的区别

1、使用newInstance可以解耦。使用newInstance的前提是,类已加载并且这个类已连接,这是正是class的静态方法forName()完成的工作。newInstance实际上是把new这个方式分解为两步,即首先调用class的加载方法加载某个类,然后实例化。
2、newInstance: 弱类型。低效率。只能调用无参构造。 new: 强类型。相对高效。能调用任何public构造。 
3、newInstance()是实现IOC、反射、面对接口编程和依赖倒置等技术方法的必然选择,new只能实现具体类的实例化,不适合于接口编程。 
4、 newInstance() 一般用于动态加载类

5、Class.forName(“”).newInstance()返回的是object 。

6、newInstance( )是一个方法,而new是一个关键字;

注:一般在通用框架里面用的就是class.forName来加载类,然后再通过反射来调用其中的方法,譬如Tomcat源码里面,这样就避免了new关键字的耦合度,还有让不同的类加载器来加载不同的类,方便提高类之间的安全性和隔离性。


数据中心机房是现代信息技术的核心设施,它承载着企业的重要数据和服务,因此,其基础设计与规划至关重要。在制定这样的方案时,需要考虑的因素繁多,包括但不限于以下几点: 1. **容量规划**:必须根据业务需求预测未来几年的数据处理和存储需求,合理规划机房的规模和设备容量。这涉及到服务器的数量、存储设备的容量以及网络带宽的需求等。 2. **电力供应**:数据中心是能源消耗大户,因此电力供应设计是关键。要考虑不间断电源(UPS)、备用发电机的容量,以及高效节能的电力分配系统,确保电力的稳定供应并降低能耗。 3. **冷却系统**:由于设备密集运行,散热问题不容忽视。合理的空调布局和冷却系统设计可以有效控制机房温度,避免设备过热引发故障。 4. **物理安全**:包括防火、防盗、防震、防潮等措施。需要设计防火分区、安装烟雾探测和自动灭火系统,设置访问控制系统,确保只有授权人员能进入。 5. **网络架构**:规划高速、稳定、冗余的网络架构,考虑使用光纤、以太网等技术,构建层次化网络,保证数据传输的高效性和安全性。 6. **运维管理**:设计易于管理和维护的IT基础设施,例如模块化设计便于扩展,集中监控系统可以实时查看设备状态,及时发现并解决问题。 7. **绿色数据中心**:随着环保意识的提升,绿色数据中心成为趋势。采用节能设备,利用自然冷源,以及优化能源管理策略,实现低能耗和低碳排放。 8. **灾难恢复**:考虑备份和恢复策略,建立异地灾备中心,确保在主数据中心发生故障时,业务能够快速恢复。 9. **法规遵从**:需遵循国家和地区的相关法律法规,如信息安全、数据保护和环境保护等,确保数据中心的合法运营。 10. **扩展性**:设计时应考虑到未来的业务发展和技术进步,保证机房有充足的扩展空间和升级能力。 技术创新在数据中心机房基础设计及规划方案中扮演了重要角色。例如,采用虚拟化技术可以提高硬件资源利用率,软件定义网络(SDN)提供更灵活的网络管理,人工智能和机器学习则有助于优化能源管理和故障预测。 总结来说,一个完整且高效的数据中心机房设计及规划方案,不仅需要满足当前的技术需求和业务目标,还需要具备前瞻性和可持续性,以适应快速变化的IT环境和未来可能的技术革新。同时,也要注重经济效益,平衡投资成本与长期运营成本,实现数据中心的高效、安全和绿色运行。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值