欢迎来到天天文库
浏览记录
ID:39468932
大小:718.00 KB
页数:25页
时间:2019-07-04
《Tomcat的系统架构与模式设计分析》由会员上传分享,免费在线阅读,更多相关内容在教育资源-天天文库。
Tomcat的系统架构与设计模式(君山,11/20/2009)如有错误之处请指出(junshan@taobao.com)前言:从标题看上去很大,Tomcat很复杂,不是一篇文章就能说清楚的,就算我想说恐怕我也说不清楚,我主要是想知道Tomcat如何分发请求的,如何处理多用户同时请求的,还有他的容器是如何工作的。这也是一个Web服务器要解决的两个关键问题。这也是我写这个文档的目的。一、Tomcat总体设计这里所说的Tomcat是以Tomcat5为基础,也兼顾最新的Tomcat6。Tomcat基本的设计思路还是有连贯性的。1.Tomcat总体结构Tomcat的结构复杂,但是Tomcat也非常的模块化,找到了Tomcat最主要的模块其实Tomcat就能被你抓住“七寸”了。下面是Tomcat的总体结构图:图1.Tomcat的总体结构从上图中可以看出Tomcat的心脏是两个组件:Connector和Container,关于这两个组件将在后面详细介绍。Connector组件是可以被替换,这样可以提供给服务器设计者更多的选择,因为这个组件是如此重要,不仅跟服务器的设计者本身,而且和不同的应用场景十分相关,所以一个Container可以选择对应多个Connector。多个Connector和一个Container就形成了一个Service,Service的概念大家都很熟悉了,有了Service就可以对外提供服务了,但是Service还要一个生存的环境,必须要有人能够给她生命、掌握其生死大权,那就非Server莫属了。所以整个Tomcat的生命周期由Server控制。1.1以Service作为“婚姻” 我们将Tomcat中Connector、Container和其他一些组件作为一个整体比作一对情侣的话,Connector主要负责对外交流,可以比作为Boy,Container主要处理Connector接受的请求,主要是处理内部事务,可以比作为Girl。那么这个Service就是连接这对男女的结婚证了。是Service将他们连接在一起,共同组成一个家庭。说白了,Service只是在Connector和Container外面多包一层,把他们组装在一起,向外面提供服务,一个Service可以设置多个Connector,但是只能有一个Container容器。这个Service接口的类图如下:图2.Service类图从Service接口中定义的方法中可以看出,它主要是为了关联Connector和Container,同时会初始化它下面的其他组件,注意接口中它并没有规定他一定要控制了它下面的组件的生命周期。所有组件的生命周期在一个Lifecycle的接口中控制,这里用到了一个重要的设计模式,关于这个接口将在后面介绍。Tomcat中Service接口的标准实现类是StandardService它不仅实现了Service接口同时还实现了Lifecycle以可以控制它下面的组件的生命周期。StandardService类结构图如下: 图3StandardService的类结构图从上图中可以看出除了Service接口的方法的实现以及控制组件生命周期的Lifecycle接口的实现,还有几个方法是用于在事件监听的方法的实现,不仅是这个Service组件,Tomcat中其他组件也同样有这几个方法,这也是一个典型的设计模式,将在后面介绍。下面看一下StandardService中主要的几个方法实现的代码,下面是setContainer(Container)和addConnector(Connector)方法的源码:publicvoidsetContainer(Containercontainer){ContaineroldContainer=this.container;if((oldContainer!=null)&&(oldContainerinstanceofEngine))((Engine)oldContainer).setService(null);this.container=container;if((this.container!=null)&&(this.containerinstanceofEngine))((Engine)this.container).setService(this);if(started&&(this.container!=null)&&(this.containerinstanceofLifecycle)){try{((Lifecycle)this.container).start();}catch(LifecycleExceptione){;}}synchronized(connectors){for(inti=0;i0)&&(curProcessors>=maxProcessors))break;HttpProcessorprocessor=newProcessor();recycle(processor);}}threadStart()执行就会进入等待请求的状态,知道一个请求到来才会激活它继续执行,这个激活时在HttpProcessor的assign方法中,这个方法是代码如下: synchronizedvoidassign(Socketsocket){//WaitfortheProcessortogetthepreviousSocketwhile(available){try{wait();}catch(InterruptedExceptione){}}//StorethenewlyavailableSocketandnotifyourthreadthis.socket=socket;available=true;notifyAll();if((debug>=1)&&(socket!=null))log("Anincomingrequestisbeingassigned");}创建HttpProcessor对象是会把available设为false,所以当请求到来时不会进入while循环,将请求的socket赋给当期处理的socket,并将available设为true,当available设为true是HttpProcessor的run方法将被激活将会处理这次请求。Run方法代码如下:publicvoidrun(){//Processrequestsuntilwereceiveashutdownsignalwhile(!stopped){//WaitforthenextsockettobeassignedSocketsocket=await();if(socket==null)continue;//Processtherequestfromthissockettry{process(socket);}catch(Throwablet){log("process.invoke",t);}//Finishupthisrequestconnector.recycle(this);}//TellthreadStop()wehaveshutourselvesdownsuccessfullysynchronized(threadSync){threadSync.notifyAll();}}解析socket的过程在process方法中,process方法的代码片段如下:privatevoidprocess(Socketsocket){booleanok=true;booleanfinishResponse=true;SocketInputStreaminput=null;OutputStreamoutput=null;try{input=newSocketInputStream(socket.getInputStream(),connector.getBufferSize());}catch(Exceptione){ log("process.create",e);ok=false;}keepAlive=true;while(!stopped&&ok&&keepAlive){finishResponse=true;try{request.setStream(input);request.setResponse(response);output=socket.getOutputStream();response.setStream(output);response.setRequest(request);((HttpServletResponse)response.getResponse()).setHeader("Server",SERVER_INFO);}catch(Exceptione){log("process.create",e);ok=false;}try{if(ok){parseConnection(socket);parseRequest(input,output);if(!request.getRequest().getProtocol().startsWith("HTTP/0"))parseHeaders(input);if(http11){ackRequest(output);if(connector.isChunkingAllowed())response.setAllowChunking(true);}}。。。。。。try{((HttpServletResponse)response).setHeader("Date",FastHttpDateFormat.getCurrentDate());if(ok){connector.getContainer().invoke(request,response);}。。。。。。if(finishResponse){try{response.finishResponse();}catch(IOExceptione){ok=false;}catch(Throwablee){log("process.invoke",e);ok=false;}try{request.finishRequest();}catch(IOExceptione){ok=false;}catch(Throwablee){log("process.invoke",e);ok=false;}try{if(output!=null)output.flush();}catch(IOExceptione){ok=false;}}if("close".equals(response.getHeader("Connection"))){keepAlive=false;}status=Constants.PROCESSOR_IDLE; request.recycle();response.recycle();}try{shutdownInput(input);socket.close();}catch(IOExceptione){;}catch(Throwablee){log("process.invoke",e);}socket=null;}当Connector将socket连接封装成request和response对象后接下来的事情就交给Container来处理了。1.Servlet容器“Container”Container是容器的接口,所有子容器都必须实现这个接口,Container容器的设计时典型的责任链的设计模式,他有四个组件构成分别是:Engine、Host、Context、Wrapper,这四个组件不是平行的,而是父子关系,Engine包含Host,Host包含Context,Context包含Wrapper。通常一个Servletclass对应一个Wrapper,如果有多个Servlet就可以定义多个Wrapper,如果有多个Wrapper就要定义一个更高的Container了,如Context,Context通常就是对应下面这个配置:3.1容器的总体设计Context还可以定义在父容器Host中,Host不是必须的,但是要运行war程序,就必须Host的,应为war中必有web.xml文件,这个文件的解析就需要Host了,如果要有多个Host就要定义一个top容器Engine了。Engine没有父容器了,一个Engine代表一个完整的Servlet引擎。那么这些容器是如何协同工作的呢?先看一下他们之间的类图: 图8四个容器的类图下面看一下,当Connector接受到一个连接请求时,将请求交给Container,Container是如何处理这个请求的?这四个组件是怎么分工,怎么把请求传给特的子容器的呢?又是如何将最终的请求交给Servlet处理。下面是这个过程的时序图: 图9Engine和Host处理请求的时序图这里看到了Valve是不是很熟悉,没错webx中也有的Valve,而且webx中的Pipeline和这里的原理也基本是相似的,都是一个管道,Engine和Host都会执行这个Pipeline,你可以在这个管道上增加任意的Valve,Tomcat会挨个执行这些Valve,而且四个组件都会有自己的一套Valve集合,你怎么才能定义自己的Valve呢?在server.xml文件中的相应的组件范围内,如给Engine和Host增加一个Valve如下:。。。。。。。。。。。。。。。。 StandardEngineValve和StandardHostValve是Engine和Host的默认的Valve,他们是最后一个Valve负责将请求传给他们的子容器,以继续往下执行。前面是Engine和Host容器的请求过程,下面看Context和Wrapper容器时如何处理请求的。下面是时序图:图10Context和wrapper的处理请求时序图从Tomcat5开始容器的路由放在了request中,request中保存了,当前请求正在处理的Host、Context和wrapper。3.2Engine容器Engine容器比较简单,它只定义了一些基本的关联关系,接口类图如下: 图11Engine接口的类结构他的标准实现类是StandardEngine,这个类注意一点就是Engine没有父容器了,如果调用setParent方法时将会报错。添加子容器也只能是Host类型的,代码如下:publicvoidaddChild(Containerchild){if(!(childinstanceofHost))thrownewIllegalArgumentException(sm.getString("standardEngine.notHost"));super.addChild(child);}publicvoidsetParent(Containercontainer){thrownewIllegalArgumentException(sm.getString("standardEngine.notParent"));}他的初始化方法也就是初始化和它相关联的组件,以及一些事件的监听。3.3Host容器Host是Engine的子容器,一个Host在Engine中代表一个虚拟主机,这个虚拟主机的责任就是运行多个应用,他负责安装盒展开这些应用,并且能够标识这个应用以便能够区分它们。它的子容器通常是Context。他除了关联子容器外,还有就是保存一个主机应该有的信息。下面是和Host相关的类图: 图12Host相关的类图从上图中可以看出除了所有容器都继承的ContainerBase外,StandardHost还实现了Deployer接口,上图清楚的列出了这个接口的主要方法,这些方法都是安装、展开、启动和结束每个webapplication。Deployer接口的实现是StandardHostDeployer,这个类中实现的最要的几个方法,Host可以调用这些方法。3.4Context容器Context代表Servlet的Context。它更能表示成Servlet的容器,他具备了Servlet运行的基本环境,理论上只要有Context就能运行Servlet了。简单的Tomcat可以没有Engine和Host。Context最重要的功能就是管理它里面的Servlet实例,Servlet实例在Context中是以Wrapper出现的,还有一点就是Context如何才能找到正确的Servlet来执行它呢?Tomcat5以前是通过一个Mapper类来管理的,Tomcat5以后这个功能被移到了request中,在前面的时序图中,可以发现获取子容器都是通过request来分配的。Context准备Servlet的运行环境是在Start方法开始的,这个方法的代码片段如下:publicsynchronizedvoidstart()throwsLifecycleException{。。。。。if(!initialized){try{init(); }catch(Exceptionex){thrownewLifecycleException("Errorinitializaing",ex);}}。。。。。//NotifyourinterestedLifecycleListenerslifecycle.fireLifecycleEvent(BEFORE_START_EVENT,null);setAvailable(false);setConfigured(false);booleanok=true;//SetconfigfilenameFileconfigBase=getConfigBase();if(configBase!=null){if(getConfigFile()==null){Filefile=newFile(configBase,getDefaultConfigFile());setConfigFile(file.getPath());//IfthedocbaseisoutsidetheappBase,weshouldsaveour//configtry{FileappBaseFile=newFile(getAppBase());if(!appBaseFile.isAbsolute()){appBaseFile=newFile(engineBase(),getAppBase());}StringappBase=appBaseFile.getCanonicalPath();StringbasePath=(newFile(getBasePath())).getCanonicalPath();if(!basePath.startsWith(appBase)){Serverserver=ServerFactory.getServer();((StandardServer)server).storeContext(this);}}catch(Exceptione){log.warn("Errorstoringconfigfile",e);}}else{try{StringcanConfigFile=(newFile(getConfigFile())).getCanonicalPath();if(!canConfigFile.startsWith(configBase.getCanonicalPath())){Filefile=newFile(configBase,getDefaultConfigFile());if(copy(newFile(canConfigFile),file)){setConfigFile(file.getPath());}}}catch(Exceptione){log.warn("Errorsettingconfigfile",e);}}}。。。//Startourchildcontainers,ifanyContainerchildren[]=findChildren();for(inti=0;i当这个reloadable设为true时,war被修改后Tomcat会自动的重新加载这个应用。如何做到这点的呢?这个功能是在StandardContext的backgroundProcess方法中实现的,这个方法的代码如下:publicvoidbackgroundProcess(){if(!started)return;count=(count+1)%managerChecksFrequency;if((getManager()!=null)&&(count==0)){try{getManager().backgroundProcess();}catch(Exceptionx){log.warn("Unabletoperformbackgroundprocessonmanager",x);}}if(getLoader()!=null){if(reloadable&&(getLoader().modified())){try{Thread.currentThread().setContextClassLoader(StandardContext.class.getClassLoader());reload();}finally{if(getLoader()!=null){Thread.currentThread().setContextClassLoader(getLoader().getClassLoader());}}}if(getLoader()instanceofWebappLoader){((WebappLoader)getLoader()).closeJARs(false);}}}他会调用reload方法,而reload方法会先调用stop方法然后在调用Start方法,完成Context的一次重新加载。可以看出执行reload方法的条件是reloadable为true和应用被修改,那么这个backgroundProcess方法是怎么被调用的呢?这个方法是在ContainerBase类中定义的内部类ContainerBackgroundProcessor被周期调用的,这个类是运行在一个后台线程中,他会周期的执行run方法,他的run方法会周期调用所有容器的backgroundProcess方法,因为所有容器都会继承ContainerBase类,所以所有容器都能够在backgroundProcess方法中定义周期执行的事件。3.5Wrapper容器Wrapper代表一个Servlet,他负责管理一个Servlet,包括的Servlet的装载、初始化、执行以及资源回收。Wrapper值最底层的容器,他没有子容器了,所以调用它的addChild将会报错。Wrapper的实现类是StandardWrapper,StandardWrapper还实现了拥有一个Servlet初始化信息的ServletConfig,由此看出StandardWrapper将直接和Servlet的各种信息打交道。下面看一下非常重要的一个方法loadServlet,代码片段如下:publicsynchronizedServletloadServlet()throwsServletException{。。。。Servletservlet;try{。。。。ClassLoaderclassLoader=loader.getClassLoader();。。。。ClassclassClass=null;。。。。。servlet=(Servlet)classClass.newInstance(); //SpecialhandlingforContainerServletinstancesif((servletinstanceofContainerServlet)&&(isContainerProvidedServlet(actualClass)||((Context)getParent()).getPrivileged())){((ContainerServlet)servlet).setWrapper(this);}classLoadTime=(int)(System.currentTimeMillis()-t1);//Calltheinitializationmethodofthisservlettry{instanceSupport.fireInstanceEvent(InstanceEvent.BEFORE_INIT_EVENT,servlet);if(System.getSecurityManager()!=null){Class[]classType=newClass[]{ServletConfig.class};Object[]args=newObject[]{((ServletConfig)facade)};SecurityUtil.doAsPrivilege("init",servlet,classType,args);}else{servlet.init(facade);}//InvokejspInitonJSPpagesif((loadOnStartup>=0)&&(jspFile!=null)){。。。。if(System.getSecurityManager()!=null){Class[]classType=newClass[]{ServletRequest.class,ServletResponse.class};Object[]args=newObject[]{req,res};SecurityUtil.doAsPrivilege("service",servlet,classType,args);}else{servlet.service(req,res);}}instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_INIT_EVENT,servlet);。。。。。。。returnservlet;}他基本上描述了对Servlet的操作,当装载了Servlet后就会调用Servlet的init方法,同时会传一个StandardWrapperFacade对象给Servlet,这个对象包装了StandardWrapper,ServletConfig与它们的关系图如下:图13ServletConfig与StandardWrapperFacade、StandardWrapper的关系Servlet可以获得的信息都在StandardWrapperFacade封装,这些信息又是在StandardWrapper对象中拿到的。所以Servlet可以通过ServletConfig拿到有限的容器的信息。当Servlet被初始化完成后,就等着StandardWrapperValve去调用它的service方法了,调用service方法之前要调用Servlet所有的filter。 1.Tomcat中其他组件Tomcat还有其他重要的组件,如安全组件security、logger日志组件、session、mbeans、naming等其他组件。这些组件共同为Connector和Container提供必要的服务。一、Tomcat中的设计模式Tomcat中一个最容易发现的设计模式就是责任链模式,这个设计模式也是Tomcat中Container设计的基础,整个容器的就是通过一个链连接在一起,这个链一直将请求正确的传递给最终处理请求的那个Servlet。当然还有其他的设计模式如模版模式等一些常用的设计模式,在《Webx框架的系统架构与设计模式》一文也有介绍。下面就还没有在《Webx框架的系统架构与设计模式》一文中介绍的一些Tomcat中的设计模式详细讲解。1.门面设计模式门面设计模式在Tomcat中有多处使用,在Request和Response对象封装中、StandardWrapper到ServletConfig封装中、ApplicationContext到ServletContext封装中等都用到了这种设计模式。1.1门面设计模式的原理这么多场合都用到了这种设计模式,那这种设计模式究竟能有什么作用呢?顾名思义,就是将一个东西封装成一个门面好与人家更容易进行交流。就像我们国家与别的国家与外国进行打交道,主要通过外交部一样。不论我们国家内部有怎样的事情发生,最终对外部发布的消息都以外交部为准。这种设计模式主要用在一个大的系统中有多个子系统组成时,这多个子系统肯定要涉及到相互通信,但是每个子系统有不能将自己的内部数据过多的暴露给其他系统,不然就没有必要划分子系统了。每个子系统都会设计一个门面,把别的系统感兴趣的数据封装起来,通过这个门面来进行访问。这就是门面设计模式存在的意义。门面设计模式示意图如下:图14门面示意图Client只能访问到Façade中提供的数据是门面设计模式的关键,至于Client如何访问Façade和Subsystem如何提供Façade门面设计模式并没有规定死。1.2Tomcat的门面设计模式示例Tomcat中门面设计模式使用的很多,因为Tomcat中有很多组件组成,每个组件要相互交互数据,隔离数据的方式用门面模式是个很好的方式。下面是Request上使用的门面设计模式: 图15Request的门面设计模式类图从图中可以看出HttpRequestFacade类封装了HttpRequest接口能够提供数据,通过HttpRequestFacade访问到的数据都被代理到HttpRequest中,通常被封装的对象都被设为Private或者Protected访问修饰,以防止在Façade中被直接访问。下面是封装StandardWrapper的类图: 图16封装StandardWrapper的类图StandardWrapperFacade封装了StandardWrapper,但是StandardWrapperFacade又继承了ServletConfig接口,这样StandardWrapper的门面就可以以ServletConfig类类型在Servlet中传递了。1.观察者设计模式这种设计模式也是常用的设计方法通常也叫发布-订阅模式,也就是事件监听机制,通常在某个事件发生的前后会触发一些操作。2.1观察者模式的原理观察者模式原理也很简单,就是你在做事的时候旁边总有一个人在盯着你,当你做到事情是他感兴趣的时候,他就会跟着做另外一些事情。但是盯着你的人必须要到你那去登记,不然你无法通知他。下面是观察者模式的结构图:图17观察者模式的结构图lSubject就是抽象主题:他负责管理所有观察者的引用,同时定义主要的事件操作。lConcreteSubject具体主题:他实现了抽象主题的所有定义的接口,当自己发生变化时,会通知所有观察者。lObserver观察者:监听主题发生变化相应的操作接口2.2Tomcat的观察者模式示例Tomcat中观察者模式也有多处使用,前面讲的控制组件生命周期的Lifecycle就是这种模式的体现,还有对Servlet实例的创建、Session的管理、Container等都是同样的原理。下面主要看一下Lifecycle的具体实现。Lifecycle的观察者模式结构图: 图18Lifecycle的观察者模式结构图上面的结构图中,LifecycleListener代表的是抽象观察者,它定义一个lifecycleEvent方法,这个方法就是当主题变化时要执行的方法。ServerLifecycleListener代表的是具体的观察者,他实现了LifecycleListener接口的方法,就是这个具体的观察者具体的实现方式。Lifecycle接口代表的是抽象主题,它定义了管理观察者的方法和他要所做的其他方法。而StandardServer代表的是具体主题,他实现了抽象主题的所有方法。这里Tomcat对观察者做了扩展,增加了另外两个类:LifecycleSupport、LifecycleEvent,他们作为辅助类扩展了观察者的功能。LifecycleEvent使得可以定义事件类别,不同的事件可区别处理,更加灵活。LifecycleSupport类代理了主题的多观察者的管理,将这个管理抽出来统一实现,以后如果修改只要修改LifecycleSupport类就可以了,不需要去修改所有具体主题,因为所有具体主题的对观察者的操作都被代理给LifecycleSupport类了。这可以认为是观察者模式的改进版。LifecycleSupport调用观察者的方法代码如下:publicvoidfireLifecycleEvent(Stringtype,Objectdata){LifecycleEventevent=newLifecycleEvent(lifecycle,type,data);LifecycleListenerinterested[]=null;synchronized(listeners){interested=(LifecycleListener[])listeners.clone();}for(inti=0;i
此文档下载收益归作者所有
举报原因
联系方式
详细说明
内容无法转码请点击此处