性能文章>Tomcat分析--(一)从架构角度看Tomcat 对应用层协议的支持和底层socket的读写的支持的高内聚低耦合设计>

Tomcat分析--(一)从架构角度看Tomcat 对应用层协议的支持和底层socket的读写的支持的高内聚低耦合设计原创

7月前
3536116

Tomcat分析系列针对于Tomcat版本:9.0.38

本文目标:

从整体了解Tomcat源码包结构,Tomcat对各个模块如何解耦的,及其各模块怎么组装的,也就是高内聚低耦合。

知道各个模块对上层模块提供获取数据的接口API,知道模块内部的入口或者说启动类,知道上层是如何对下层数据做处理的

 

Tomcat源码包结构

自下而上分析包功能,主要3大模块:

tomcat.util.net包:

封装底层对Socket的网络连接和数据传输,封装了对底层Socket的数据的读、写。

Java实现IO进行网络数据传输的方式有3种方式:

  • BIO:同步阻塞IO。原生的Socket代码编写
  • NIO:同步非阻塞IO。基于Channel和Buffer的设计
  • NIO2(AIO):异步非阻塞IO

coyote包:

  • Tomcat连接器(对应用层协议的封装,比如HTTP、HTTP/2、HTTPS),同样提供了对外的接口用于获取应用层协议数据。
  • coyote包是Tomcat服务器提供的供客户端访问的外部接口。客户端通过Coyote与服务器建立链接、发送请求并且接收响应。

catalina包:Servlet容器实现

其他:

  • juli包:Tomcat日志的实现

下面分析各个模块

net包

1.功能

与底层Socket打交道,隐藏底层对Socket的实现(NIO、NIO2、BIO

2.net本包内对传输层的抽象入口API:AbstractEndpoint

也就是上层模块如果依赖于net包实现Socket的处理,需要提供 AbstractEndpoint的实现类。

3.对外提供获取底层Socket数据入口的接口API指的是net包提供对Socket信息的封装,提供一个接口供外部通过该接口获取到Socket底层操作。(这里的外部指的是coyote包,coyote来对接下游net,对接上游catalina)

org.apache.tomcat.util.net.AbstractEndpoint.Handler

  1. 主要方法:
    process方法:对底层的Socket封装了(SocketWrapperBase,还有一个SocketEvent),使用SocketWrapperBase对外提供对底层Socket操作的封装。

tomcat提供对Handler的实现类:

coyote包:org.apache.coyote.AbstractProtocol.ConnectionHandler(其内部又委托coyote包下的org.apache.coyote.AbstractProtocol),在ConnectionHandler的内部,又将处理交给org.apache.coyote.Processor处理。

在哪里组装Handler?

在AbstractEndpoint内部有个属性:

private Handler<S> handler = null;

 

Coyote包

1.功能

封装应用层协议的处理

2.coyote本包内部功能模块的设计入口API:org.apache.coyote.ProtocolHandler,应用层协议处理的抽象

coyote包都是围绕ProtocolHandler接口设计的。

 

ProtocolHandler内部聚合一个Adapter来对上层提供服务。

3.对上层(catalina)提供的数据入口API:org.apache.coyote.Adapter   

注意:这里的Request、Response是coyote对应用层协议(HTTP)数据的封装,提供对外的一致性访问API。

  • org.apache.coyote.Request
  • org.apache.coyote.Response

 prepare方法:返回true,则继续执行后面的操作,比如执行service方法。返回false,则直接通过org.apache.catalina.connector.Response返回异常,比如调用org.apache.catalina.connector.Response的sendError直接返回用户。

service:该方法就是上层模块处理请求逻辑的入口了。

Tomcat提供org.apache.coyote.Adapter的实现类:

在catalina包下:org.apache.catalina.connector.CoyoteAdapter
在CoyoteAdapter内部浮点数

catalina包

通过Connector类来组装下游接口API,同时组装上游的API(Service接口),也就是Connector是Tomcat容器和底层数据读写的解耦。

在构造函数中根据应用层协议类型创建了具体的ProtocolHandler实现类:

public Connector() {
        this("HTTP/1.1");
    }


    public Connector(String protocol) {
        boolean apr = AprStatus.isAprAvailable() &&
            AprStatus.getUseAprConnector();
        ProtocolHandler p = null;
        try {
            p = ProtocolHandler.create(protocol, apr);
        } catch (Exception e) {
            log.error(**.getString(
                    "coyoteConnector.protocolHandlerInstantiationFailed"), e);
        }
        if (p != null) {
            protocolHandler = p;
            protocolHandlerClassName = protocolHandler.getClass().getName();
        } else {
            protocolHandler = null;
            protocolHandlerClassName = protocol;
        }
        // Default for Connector depends on this system property
        setThrowOnFailure(Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE"));
    }

 org.apache.coyote.ProtocolHandler#create方法:

public static ProtocolHandler create(String protocol, boolean apr)
            throws ClassNotFoundException, InstantiationException, IllegalAccessException,
            IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {
        if (protocol == null || "HTTP/1.1".equals(protocol)
                || (!apr && org.apache.coyote.http11.Http11NioProtocol.class.getName().equals(protocol))
                || (apr && org.apache.coyote.http11.Http11AprProtocol.class.getName().equals(protocol))) {
            if (apr) {
                return new org.apache.coyote.http11.Http11AprProtocol();
            } else {
                return new org.apache.coyote.http11.Http11NioProtocol();
            }
        } else if ("AJP/1.3".equals(protocol)
                || (!apr && org.apache.coyote.ajp.AjpNioProtocol.class.getName().equals(protocol))
                || (apr && org.apache.coyote.ajp.AjpAprProtocol.class.getName().equals(protocol))) {
            if (apr) {
                return new org.apache.coyote.ajp.AjpAprProtocol();
            } else {
                return new org.apache.coyote.ajp.AjpNioProtocol();
            }
        } else {
            // Instantiate protocol handler
            Class<?> clazz = Class.forName(protocol);
            return (ProtocolHandler) clazz.getConstructor().newInstance();
        }
    }

问题:对于HTTP协议,创建的类是Http11NioProtocol,为什么带有Nio标识呢?因为需要创建具体的AbstractEndpoint实现类,通过类名就可以知道内部提供了什么样的AbstractEndpoint实现类。

public Http11NioProtocol() {
        //new 了一个NioEndpoint
        super(new NioEndpoint());
    }

 

总结

用图形化表示3个模块是如何组装的:

模块代码设计提示:

通常一个模块的设计入口类或者说启动类,都不是真正干活的人,启动类有两个作用:

  • 封装内部实现细节
  • 起到承上作用。也就是其抽象类内部不仅包含了其当前模块的各个功能实现的抽象接口,还包含服务于上层模块的抽象API,这个抽象API通常就是调用当前模块返回的数据。

比如net包的AbstractEndpoint:呈上:private Handler<S> handler = null; 封装内部实现细节:protected Acceptor<U> acceptor,还有其他组件共同完成内部实现。

 

源码分析下HTTP请求处理的整个大致过程:

入口:catalina包下的:从Connector的构造函数开始,Connector前面我们先不讲,本篇文章主要讲catalina容器之外的与底层数据有关联的流程

以后几篇先分析net包内部的功能设计,主要关注以下问题

net包

线程模型:

  • reactor线程模型,与netty的不同点在哪里?为什么需要这样设计?
  • tomcat默认基于阻塞的accept,而不是像netty基于Selector的accept事件触发的,tomcat为什么这样设计?

net包如何基于NIO、NIO2实现底层数据通信的,既然支持两种IO模型,那么net包如何屏蔽掉这种差异的而使用统一的流程去处理逻辑的?

net包内部是如何进行状态自动切换的,比如accpet线程、Poller线程、Executor线程执行器,知道这些可以让我们知道该如何根据内部状态码的切换自动应对各种逻辑的处理

对底层Socket的读写有哪些优化?

超时处理:Tomcat在哪里触发超时逻辑。tomcat的超时分为两大类:异步Servlet超时、Socket相关的超时(读写超时、连接超时)。tomcat如何实现超时的?

配置:对底层tcp的配置参数有哪些

 

 

分类:标签:
请先登录,查看1条精彩评论吧
快去登录吧,你将获得
  • 浏览更多精彩评论
  • 和开发者讨论交流,共同进步

为你推荐

字符串字面量长度是有限制的
前言 偶然在一次单元测试中写了一个非常长的字符串字面量。 正文 在一次单元测试中,我写了一个很长的字符串字面量,大概10万个字符左右,编译时,编译器给出了异常告警 `java: constant
多次字符串相加一定要用StringBuilder而不用-吗?
今天在写一个读取Java class File并进行分析的Demo时,偶然发现了下面这个场景(基于oracle jdk 1.8.0_144): ``` package test; public c
如何通过反射获得方法的真实参数名(以及扩展研究)
前段时间,在做一个小的工程时,遇到了需要通过反射获得方法真实参数名的场景,在这里我遇到了一些小小的问题,后来在部门老大的指导下,我解决了这个问题。通过解决这个问题,附带着我了解到了很多新的知识,我觉得
高吞吐、低延迟 Java 应用的 GC 优化实践
本篇原文作者是 LinkedIn 的 Swapnil Ghike,这篇文章讲述了 LinkedIn 的 Feed 产品的 GC 优化过程,虽然文章写作于 April 8, 2014,但其中的很多内容和
「每日五分钟,玩转 JVM」:久识你名,初居我心
聊聊 JVMJVM,一个熟悉又陌生的名词,从认识Java的第一天起,我们就会听到这个名字,在参加工作的前一两年,面试的时候还会经常被问到JDK,JRE,JVM这三者的区别。JVM可以说和我们是老朋友了
据说99.99%的人都会答错的类加载的问题
概述首先还是把问题抛给大家,这个问题也是我厂同学在做一个性能分析产品的时候碰到的一个问题。 同一个类加载器对象是否可以加载同一个类文件多次并且得到多个Class对象而都可以被java层使用吗请仔细注意
Java多线程——并发测试
编写并发程序时候,可以采取和串行程序相同的编程方式。唯一的难点在于,并发程序存在不确定性,这种不确定性会令程序出错的地方远比串行程序多,出现的方式也没有固定规则。那么如何在测试中,尽可能的暴露出这些问
Java多线程知识小抄集(一)
本文主要整理笔者遇到的Java多线程的相关知识点,适合速记,故命名为“小抄集”。本文没有特别重点,每一项针对一个多线程知识做一个概要性总结,也有一些会带一点例子,习题方便理解和记忆。 1.interr