Tomcat原理扫盲

Tomcat加载类和资源的顺序为:

  1. /Web-INF/classes
  2. /Web-INF/lib/*.jar
  3. Bootstrap
  4. System
  5. $CATALINA_HOME/common/classes
  6. $CATALINA_HOME/common/endores/*.jar
  7. $CATALINA_HOME/common/lib/*.jar
  8. $CATALINA_HOME/shared/classes
  9. $CATALINA_HOME/shared/lib/*.jar

Tomcat核心组件Connecter和Container

1.Connecter

1.1Connector的功能

  • 一个Connecter将在某个指定的端口上侦听客户请求,接收浏览器的发过来的 tcp 连接请求,创建一个 Request 和 Response 对象分别用于和请求端交换数据,然后会产生一个线程来处理这个请求并把产生的 Request 和 Response 对象传给处理Engine(Container中的一部分),从Engine出获得响应并返回客户。
  • Tomcat中有两个经典的Connector,一个直接侦听来自Browser的HTTP请求,另外一个来自其他的WebServer请求。Cotote HTTP/1.1 Connector在端口8080处侦听来自客户Browser的HTTP请求,Coyote JK2 Connector在端口8009处侦听其他Web Server的Servlet/JSP请求。
  • Connector 最重要的功能就是接收连接请求然后分配线程让 Container 来处理这个请求,所以这必然是多线程的,多线程的处理Connector 设计的核心。

1.2Connector的protocol

  • protocol负责接收HTTP请求,Tomcat中支持两种协议的连接器:HTTP/1.1与AJP/1.3,默认端口8080,该两种协议有三种不同的实现方式
    :JIO(java.io—>BIO阻塞队列)、APR、NIO
  • protocol配置
<Connector port="8080" protocol="HTTP/1.1" 
               connectionTimeout="20000" 
               redirectPort="8443" URIEncoding="utf-8"/>
  • BIO实现的Connector中 接收请求的JIoEndpoint对象。JIoEndpoint维护了Acceptor和Worker:Acceptor接收socket,然后从Worker线程池中找出空闲的线程处理socket,如果worker线程池没有空闲线程,则Acceptor将阻塞。其中Worker是Tomcat自带的线程池,如果通过配置了其他线程池,原理与Worker类似。

  • 在NIO实现的Connector中,处理请求的主要实体是NIoEndpoint对象。NIoEndpoint中除了包含Acceptor和Worker外,还使用了Poller,Acceptor接收socket后,不是直接使用Worker中的线程处理请求,而是先将请求发送给了Poller,而Poller是实现NIO的关键。Acceptor向Poller发送请求通过队列实现,使用了典型的生产者-消费者模式。在Poller中,维护了一个Selector对象;当Poller从队列中取出socket后,注册到该Selector中;然后通过遍历Selector,找出其中可读的socket,并使用Worker中的线程处理相应请求。与BIO类似,Worker也可以被自定义的线程池代替。
    通过上述过程可以看出,在NIoEndpoint处理请求的过程中,无论是Acceptor接收socket,还是线程处理请求,使用的仍然是阻塞方式;但在“读取socket并交给Worker中的线程”的这个过程中,使用非阻塞的NIO实现,这是NIO模式与BIO模式的最主要区别(其他区别对性能影响较小,暂时略去不提)。而这个区别,在并发量较大的情形下可以带来Tomcat效率的显著提升:

    目前大多数HTTP请求使用的是长连接(HTTP/1.1默认keep-alive为true),而长连接意味着,一个TCP的socket在当前请求结束后,如果没有新的请求到来,socket不会立马释放,而是等timeout后再释放。如果使用BIO,“读取socket并交给Worker中的线程”这个过程是阻塞的,也就意味着在socket等待下一个请求或等待释放的过程中,处理这个socket的工作线程会一直被占用,无法释放;因此Tomcat可以同时处理的socket数目不能超过最大线程数,性能受到了极大限制。而使用NIO,“读取socket并交给Worker中的线程”这个过程是非阻塞的,当socket在等待下一个请求或等待释放时,并不会占用工作线程,因此Tomcat可以同时处理的socket数目远大于最大线程数,并发性能大大提高。
    在这里插入图片描述

1.3Connector的关键参数

  • 【acceptCount】: 接收队列的长度,队列满时,拒绝接收请求,acceptCount设置过大会导致请求等待时间长,设置过小,会立马返回拒绝链接问题
  • 【maxConnections】:最大连接数,Tomcat任意时刻接收和处理的最大连接数。当Tomcat接收的连接数达到maxConnections时,Acceptor线程不会读取accept队列中的连接;这时accept队列中的线程会一直阻塞着,直到Tomcat接收的连接数小于maxConnections。
  • 【maxThreads】:Tomcat的最大线程数,使用BIO场景,maxConnections和maxThreads应该一致,使用NIO场景,maxConnections可以远大于maxThreads

Container

Container的体系结构

在这里插入图片描述

  • Engine 容器
    Engine 容器比较简单,它只定义了一些基本的关联关系
    Host 容器
  • Host 是 Engine 的字容器,一个 Host 在 Engine 中代表一个虚拟主机,这个虚拟主机的作用就是运行多个应用,它负责安装和展开这些应用,并且标识这个应用以便能够区分它们。它的子容器通常是 Context,它除了关联子容器外,还有就是保存一个主机应该有的信息。
  • Context 容器
    Context 代表 Servlet 的 Context,它具备了 Servlet 运行的基本环境,理论上只要有 Context 就能运行 Servlet 了。简单的 Tomcat 可以没有 Engine 和 Host。Context 最重要的功能就是管理它里面的 Servlet 实例,Servlet 实例在 Context 中是以 Wrapper 出现的,还有一点就是 Context 如何才能找到正确的 Servlet 来执行它呢? Tomcat5 以前是通过一个 Mapper 类来管理的,Tomcat5 以后这个功能被移到了 request 中,在前面的时序图中就可以发现获取子容器都是通过 request 来分配的。
  • Wrapper 容器
    Wrapper 代表一个 Servlet,它负责管理一个 Servlet,包括的 Servlet 的装载、初始化、执行以及资源回收。Wrapper 是最底层的容器,它没有子容器了,所以调用它的 addChild 将会报错。
    Wrapper 的实现类是 StandardWrapper,StandardWrapper 还实现了拥有一个 Servlet 初始化信息的 ServletConfig,由此看出 StandardWrapper 将直接和 Servlet 的各种信息打交道。

Tomcat处理HTTP请求的过程

在这里插入图片描述

  1. 用户点击网页内容,请求被发送到本机端口8080,被在那里监听的Coyote HTTP/1.1 Connector获得。
  2. Connector把该请求交给它所在的Service的Engine来处理,并等待Engine的回应。
  3. Engine获得请求localhost/test/index.jsp,匹配所有的虚拟主机Host。
  4. Engine匹配到名为localhost的Host(即使匹配不到也把请求交给该Host处理,因为该Host被定义为该Engine的默认主机),名为localhost的Host获得请求/test/index.jsp,匹配它所拥有的所有的Context。Host匹配到路径为/test的Context(如果匹配不到就把该请求交给路径名为“ ”的Context去处理)。
  5. path=“/test”的Context获得请求/index.jsp,在它的mapping table中寻找出对应的Servlet。Context匹配到URL PATTERN为*.jsp的Servlet,对应于JspServlet类。
  6. 构造HttpServletRequest对象和HttpServletResponse对象,作为参数调用JspServlet的doGet()或doPost().执行业务逻辑、数据存储等程序。
  7. Context把执行完之后的HttpServletResponse对象返回给Host。
  8. Host把HttpServletResponse对象返回给Engine。
  9. Engine把HttpServletResponse对象返回Connector。
  10. Connector把HttpServletResponse对象返回给客户Browser。
    【Tomcat一个线程处理HTTP请求的调用栈】
    在这里插入图片描述
  • 从调用栈看Tomcat主体流程:Connector读取Socket请求–>交给container容器(Engine->Host->Context)->Dofilter->Servlet分发请求对应到处理请求的实体类

Tomcat的Filter

通过Filter技术,开发人员可以实现用户在访问某个目标资源之前,对访问的请求和响应进行拦截。简单说,就是可以实现web容器对某资源的访问前截获进行相关的处理,还可以在某资源向web容器返回响应前进行截获进行处理。

  • Filter的工作流程
    在这里插入图片描述
  • Filter采用的是责任链设计模式
  • Filter的执行逻辑:
  1. 把要执行的servlet存放到过滤器链中。
  2. 如果没有配置过滤器则return一个空的过滤器链(只包含上面设置的servlet)。
  3. 如果配置过滤器,则把所有配置的过滤器加入到过滤器链中
    3.1 首先判断filter-mapping中配置的dispatcher规则,如果符合则进入下一步
    3.2 然后判断filter-mapping中配置的url-pattern规则,如果符合则添加到过滤器链
  • Tomcat过滤器的顺序是按照web.xml中的先后顺序执行的
  • Tomcat的 filter配置,init-param可以用作排除过滤器请求的声明
    <filter>
        <filter-name>fiter</filter-name>
        <filter-class>*****</filter-class>
        <init-param>
            <param-name>excludedPages</param-name>
            <param-value>/rest/.*</param-value><!-- 匹配不做拦截的请求声明-->
        </init-param>	
    </filter>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章