Tomcat

Tomcat 实现的 2 个核心功能:

  • 处理 Socket 连接,负责网络字节流与 Request 和 Response 对象的转化。

  • 加载并管理 Servlet ,以及处理具体的 Request 请求。

Tomcat架构

  • Server 对应的就是一个 Tomcat 实例。

  • Service 默认只有一个,也就是一个 Tomcat 实例默认一个 Service。

  • Connector:一个 Service 可能多个 连接器,接受不同连接协议。

  • Container: 多个连接器对应一个容器,顶层容器其实就是 Engine。

Tomcat支持的 I/O 模型有:

  • NIO:非阻塞 I/O,采用 Java NIO 类库实现。

  • NIO2:异步I/O,采用 JDK 7 最新的 NIO2 类库实现。

  • APR:采用 Apache可移植运行库实现,是 C/C++ 编写的本地库。

Tomcat 支持的应用层协议有:

  • HTTP/1.1:这是大部分 Web 应用采用的访问协议。

  • AJP:用于和 Web 服务器集成(如 Apache)。

  • HTTP/2:HTTP 2.0 大幅度的提升了 Web 性能。

连接器的功能需求就是:

  • 监听网络端口。

  • 接受网络连接请求。

  • 读取请求网络字节流。

  • 根据具体应用层协议(HTTP/AJP)解析字节流,生成统一的 Tomcat Request 对象。

  • 将 Tomcat Request 对象转成标准的 ServletRequest

  • 调用 Servlet容器,得到 ServletResponse

  • 将 ServletResponse转成 Tomcat Response 对象。

  • 将 Tomcat Response 转成网络字节流。

  • 将响应字节流写回给浏览器。

连接器需要完成 3 个高内聚的功能:

  • 网络通信。

  • 应用层协议解析。

  • Tomcat Request/Response 与 ServletRequest/ServletResponse 的转化。

即EndPoint、Processor 和 Adapter

EndPoint 负责提供字节流给 ProcessorProcessor负责提供 Tomcat Request 对象给 AdapterAdapter负责提供 ServletRequest对象给容器。

连接器的三个核心组件 EndpointProcessor和 Adapter来分别做三件事情,其中 Endpoint和 Processor放在一起抽象成了 ProtocolHandler组件,

EndPoint

EndPoint是通信监听的接口,是具体的 Socket 接收和发送处理器,是对传输层的抽象,因此 EndPoint是用来实现 TCP/IP 协议数据读写的,本质调用操作系统的 socket 接口。

EndPoint是一个接口,对应的抽象实现类是 AbstractEndpoint。而 AbstractEndpoint的具体子类,比如在 NioEndpoint和 Nio2Endpoint中,有两个重要的子组件:Acceptor和 SocketProcessor

其中 Acceptor 用于监听 Socket 连接请求。SocketProcessor用于处理 Acceptor 接收到的 Socket请求,它实现 Runnable接口,在 Run方法里调用应用层协议处理组件 Processor 进行处理。为了提高处理能力,SocketProcessor被提交到线程池来执行。

NioEndpoint:LimitLatchAcceptorPollerSocketProcessor和 Executor

  • LimitLatch 是连接控制器,它负责控制最大连接数,NIO 模式下默认是 10000,达到这个阈值后,连接请求被拒绝。

  • Acceptor跑在一个单独的线程里,它在一个死循环里调用 accept方法来接收新连接,一旦有新的连接请求到来,accept方法返回一个 Channel 对象,接着把 Channel对象交给 Poller 去处理。

  • Poller 的本质是一个 Selector,也跑在单独线程里。Poller在内部维护一个 Channel数组,它在一个死循环里不断检测 Channel的数据就绪状态,一旦有 Channel可读,就生成一个 SocketProcessor任务对象扔给 Executor去处理。

  • SocketProcessor 实现了 Runnable 接口,其中 run 方法中的 getHandler().process(socketWrapper, SocketEvent.CONNECT_FAIL); 代码则是获取 handler 并执行处理 socketWrapper,最后通过 socket 获取合适应用层协议处理器,也就是调用 Http11Processor 组件来处理请求。Http11Processor 读取 Channel 的数据来生成 ServletRequest 对象,Http11Processor 并不是直接读取 Channel 的。这是因为 Tomcat 支持同步非阻塞 I/O 模型和异步 I/O 模型,在 Java API 中,相应的 Channel 类也是不一样的,比如有 AsynchronousSocketChannel 和 SocketChannel,为了对 Http11Processor 屏蔽这些差异,Tomcat 设计了一个包装类叫作 SocketWrapper,Http11Processor 只调用 SocketWrapper 的方法去读写数据。

  • Executor就是线程池,负责运行 SocketProcessor任务类,SocketProcessor 的 run方法会调用 Http11Processor 来读取和解析请求数据。我们知道,Http11Processor是应用层协议的封装,它会调用容器获得响应,再把响应通过 Channel写出。

Processor:

Processor 用来实现 HTTP 协议,Processor 接收来自 EndPoint 的 Socket,读取字节流解析成 Tomcat Request 和 Response 对象,并通过 Adapter 将其提交到容器处理,Processor 是对应用层协议的抽象。

从图中我们看到,EndPoint 接收到 Socket 连接后,生成一个 SocketProcessor 任务提交到线程池去处理,SocketProcessor 的 Run 方法会调用 HttpProcessor 组件去解析应用层协议,Processor 通过解析生成 Request 对象后,会调用 Adapter 的 Service 方法,方法内部通过 以下代码将请求传递到容器中。

connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);

Adapter 

CoyoteAdapter,这是适配器模式的经典运用,连接器调用 CoyoteAdapter 的 Sevice 方法,传入的是 Tomcat Request 对象,CoyoteAdapter负责将 Tomcat Request 转成 ServletRequest,再调用容器的 Service方法。

Server:

Service:

Connector:连接器,由Coyote实现。

Container:容器。

 

容器

连接器负责外部交流,容器负责内部处理。具体来说就是,连接器处理 Socket 通信和应用层协议的解析,得到 Servlet请求;而容器则负责处理 Servlet请求。

Wrapper 表示一个 Servlet ,Context 表示一个 Web 应用程序,而一个 Web 程序可能有多个 Servlet ;Host 表示一个虚拟主机,或者说一个站点,一个 Tomcat 可以配置多个站点(Host);一个站点( Host) 可以部署多个 Web 应用;Engine 代表 引擎,用于管理多个站点(Host),一个 Service 只能有 一个 Engine

请求定位 Servlet 的过程

Mapper组件里保存了 Web 应用的配置信息,其实就是容器组件与访问路径的映射关系,比如 Host容器里配置的域名、Context容器里的 Web应用路径,以及 Wrapper容器里 Servlet 映射的路径,你可以想象这些配置信息就是一个多层次的 Map

整个过程分是通过连接器中的 CoyoteAdapter 触发,它会调用 Engine 的第一个 Valve:

@Override
public void service(org.apache.coyote.Request req, org.apache.coyote.Response res) {
    // 省略其他代码
    // Calling the container
    connector.getService().getContainer().getPipeline().getFirst().invoke(
        request, response);
    ...
}

 

组合模式、责任链模式、观察者模式、模板模式

 

热加载

Tomcat 本质是通过一个后台线程做周期性的任务,定期检测类文件的变化,如果有变化就重新加载类。我们来看 ContainerBackgroundProcessor具体是如何实现的。

 

类加载

  1. 先在本地 Cache 查找该类是否已经加载过,也就是说 Tomcat 的类加载器是否已经加载过这个类。

  2. 如果 Tomcat 类加载器没有加载过这个类,再看看系统类加载器是否加载过。

  3. 如果都没有,就让ExtClassLoader去加载,这一步比较关键,目的 防止 Web 应用自己的类覆盖 JRE 的核心类。因为 Tomcat 需要打破双亲委托机制,假如 Web 应用里自定义了一个叫 Object 的类,如果先加载这个 Object 类,就会覆盖 JRE 里面的那个 Object 类,这就是为什么 Tomcat 的类加载器会优先尝试用 ExtClassLoader去加载,因为 ExtClassLoader会委托给 BootstrapClassLoader去加载,BootstrapClassLoader发现自己已经加载了 Object 类,直接返回给 Tomcat 的类加载器,这样 Tomcat 的类加载器就不会去加载 Web 应用下的 Object 类了,也就避免了覆盖 JRE 核心类的问题。

  4. 如果 ExtClassLoader加载器加载失败,也就是说 JRE核心类中没有这类,那么就在本地 Web 应用目录下查找并加载。

  5. 如果本地目录下没有这个类,说明不是 Web 应用自己定义的类,那么由系统类加载器去加载。这里请你注意,Web 应用是通过Class.forName调用交给系统类加载器的,因为Class.forName的默认加载器就是系统类加载器。

  6. 如果上述加载过程全部失败,抛出 ClassNotFound异常。

Tomcat 作为 Servlet容器,它负责加载我们的 Servlet类,此外它还负责加载 Servlet所依赖的 JAR 包。并且 Tomcat本身也是也是一个 Java 程序,因此它需要加载自己的类和依赖的 JAR 包。首先让我们思考这一下这几个问题:

  1. 假如我们在 Tomcat 中运行了两个 Web 应用程序,两个 Web 应用中有同名的 Servlet,但是功能不同,Tomcat 需要同时加载和管理这两个同名的 Servlet类,保证它们不会冲突,因此 Web 应用之间的类需要隔离。

  2. 假如两个 Web 应用都依赖同一个第三方的 JAR 包,比如 Spring,那 Spring的 JAR 包被加载到内存后,Tomcat要保证这两个 Web 应用能够共享,也就是说 Spring的 JAR 包只被加载一次,否则随着依赖的第三方 JAR 包增多,JVM的内存会膨胀。

  3. 跟 JVM 一样,我们需要隔离 Tomcat 本身的类和 Web 应用的类。

1. WebAppClassLoader

Tomcat 的解决方案是自定义一个类加载器 WebAppClassLoader, 并且给每个 Web 应用创建一个类加载器实例。我们知道,Context 容器组件对应一个 Web 应用,因此,每个 Context容器负责创建和维护一个 WebAppClassLoader加载器实例。这背后的原理是,不同的加载器实例加载的类被认为是不同的类,即使它们的类名相同。这就相当于在 Java 虚拟机内部创建了一个个相互隔离的 Java 类空间,每一个 Web 应用都有自己的类空间,Web 应用之间通过各自的类加载器互相隔离。

2.SharedClassLoader

本质需求是两个 Web 应用之间怎么共享库类,并且不能重复加载相同的类。在双亲委托机制里,各个子加载器都可以通过父加载器去加载类,那么把需要共享的类放到父加载器的加载路径下不就行了吗。

因此 Tomcat 的设计者又加了一个类加载器 SharedClassLoader,作为 WebAppClassLoader的父加载器,专门来加载 Web 应用之间共享的类。如果 WebAppClassLoader自己没有加载到某个类,就会委托父加载器 SharedClassLoader去加载这个类,SharedClassLoader会在指定目录下加载共享类,之后返回给 WebAppClassLoader,这样共享的问题就解决了。

3. CatalinaClassloader

如何隔离 Tomcat 本身的类和 Web 应用的类?

要共享可以通过父子关系,要隔离那就需要兄弟关系了。兄弟关系就是指两个类加载器是平行的,它们可能拥有同一个父加载器,基于此 Tomcat 又设计一个类加载器 CatalinaClassloader,专门来加载 Tomcat 自身的类。

这样设计有个问题,那 Tomcat 和各 Web 应用之间需要共享一些类时该怎么办呢?

老办法,还是再增加一个 CommonClassLoader,作为 CatalinaClassloader和 SharedClassLoader的父加载器。CommonClassLoader能加载的类都可以被 CatalinaClassLoader和 SharedClassLoader使用

Tomcat启动流程

Tomcat请求处理流程

Tomcat服务配置

 

 

参考文献

https://mp.weixin.qq.com/s/fU5Jj9tQvNTjRiT9grm6RA

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值