七.吊打面试官系列-Tomcat优化-深入Tomcat核心架构

前言

作为一个名Java程序员,对于Tomcat肯定是很熟悉的,平时可能我们只是在Tomcat容器中部署项目,并不会去了解Tomcat底层架构,或者是对Tomcat做性能优化。然而在大型在高并发的项目中对Tomcat的优化确实必不可少的。接下来我讲带你一步一步去了解Tomcat的底层的奥秘,掌握Tomcat性能优化技能让面试官或者同事对你刮目相看。

一.IDEA导入Tomcat源码

1.下载源码

Tomcat源码下载地址: https://github.com/apache/tomcat/tree/9.0.x ,文章采用Tomcat9作为分析版本
在这里插入图片描述
下载好之后解压项目:tomcat-9.0.x ,然后在项目目录中创建一个 catalina-home 文件夹,把conf和webapps这拷贝到 catalina-home 目录中。
在这里插入图片描述
然后再tomcat-9.0.x项目根目录创建pom.xml,目的是把项目转换为Maven项目,内容如下

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.apache.tomcat</groupId>
  <artifactId>Tomcat9.0</artifactId>
  <name>Tomcat9.0</name>
  <version>9.0</version>
  <build>
    <finalName>Tomcat9.0</finalName>
    <sourceDirectory>java</sourceDirectory>
    <resources>
      <resource>
        <directory>java</directory>
      </resource>
    </resources>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>2.3</version>
        <configuration>
          <encoding>UTF-8</encoding>
          <source>1.8</source>
          <target>1.8</target>
        </configuration>
      </plugin>
    </plugins>
  </build>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>ant</groupId>
      <artifactId>ant</artifactId>
      <version>1.7.0</version>
    </dependency>
    <dependency>
      <groupId>wsdl4j</groupId>
      <artifactId>wsdl4j</artifactId>
      <version>1.6.3</version>
    </dependency>
    <dependency>
      <groupId>javax.xml</groupId>
      <artifactId>jaxrpc</artifactId>
      <version>1.1</version>
    </dependency>
    <dependency>
      <groupId>org.eclipse.jdt.core.compiler</groupId>
      <artifactId>ecj</artifactId>
      <version>4.6.1</version>
    </dependency>
    <dependency>
      <groupId>biz.aQute.bnd</groupId>
      <artifactId>biz.aQute.bndlib</artifactId>
      <version>5.2.0</version>
    </dependency>
  </dependencies>
</project>

2.配置项目

然后使用IDEA工具导入项目,导入后执行 Edit Configurations,
在这里插入图片描述
添加一个Application, 启动类要选择:org.apache.catalina.startup.Bootstrap , 并增加VM参数

-Dcatalina.home=catalina-home
-Dcatalina.base=catalina-home
-Djava.endorsed.dirs=catalina-home/endorsed
-Djava.io.tmpdir=catalina-home/temp
-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
-Djava.util.logging.config.file=catalina-home/conf/logging.properties

在这里插入图片描述
JDK版本需要选择1.8
在这里插入图片描述

3.处理报错

启动项目,启动项目的时候会出现一些错误,直接代码删除即可,比如下面是检查JDK版本的代码,爆红的地方直接删除掉在这里插入图片描述
启动的时候控制台会出现乱码问题,找到 org.apache.tomcat.util.res.StringManager类的getString(final String key, final Object… args)方法,将代码更改为:(注意这里getString()方法有两个,注意参数)

public String getString(final String key, final Object... args) {
        String value = getString(key);
        if (value == null) {
            value = key;
        }
        try{
            value = new String(value.getBytes("ISO-8859-1"),"UTF-8");
        }catch (Exception e ){
            e.printStackTrace();
        }
 
        MessageFormat mf = new MessageFormat(value);
        mf.setLocale(locale);
        return mf.format(args, new StringBuffer(), null).toString();
    }

找到 org.apache.jasper.compiler.Localizer类的getMessage(String errCode)方法,将代码更改为:

public static String getMessage(String errCode) {
        String errMsg = errCode;
        try {
            if (bundle != null) {
                errMsg = bundle.getString(errCode);
            }
        } catch (MissingResourceException e) {
        }
        try{
            errMsg = new String(errMsg.getBytes("ISO-8859-1"),"UTF-8");
        }catch (Exception e ){
            e.printStackTrace();
        }
        return errMsg;
    }

JSP引擎初始化,在org.apache.catalina.startup.ContextConfig类中的protected synchronized void configureStart()方法下的webConfig();语句下面添加语句:

context.addServletContainerInitializer(new JasperInitializer(),null);

重启服务器,访问8080端口
在这里插入图片描述

二.Tomcat的底层架构

Tomcat的三种部署方式如下:

  1. 直接把项目拷贝到 Tomcat的webapps 目录下
  2. 把项目打war包,将这个war文件放到Tomcat的“webapps”目录下
  3. 在“server.xml”中配置外部目录的路径,使Tomcat能够访问到这个目录。比如:
<Context docBase="项目路径" path=""  reloadable="true" />

下面是一份tomcat的配置文件 server.xml

<Server>    					//服务器配置,可以包括多个Service
    <Service>  					//顶层组件,可包含一个Engine,多个连接器
        <Connector/>			//连接器组件,Connector是用来处理通信的           
        <Engine>				//容器组件,一个Engine组件处理Service中的所有请求,包含多个Host
            <Host>  			//容器组件,处理特定的Host下客户请求,可包含多个Context
                <Context/> 		//容器组件,为特定的Web应用处理所有的客户请求
        </Host>
        </Engine>
    </Service>    
</Server>    

在Tomat中,不同的配置元素对应了不同的组件,配置文件可以看出,它的底层结构由一下几个部分组成:Server ,Service,Connector,Engine,Host,Context 他们的关系如下图:
在这里插入图片描述

1.Server

在Tomcat中最顶层的容器是:Server,代表着一个Tomcat服务器,一个Server下可以包含多个Service,这样可以实现通过不同的端口号来访问同一台机器上部署的不同应用

2.Service

Service :Service主要包含两个部分:Connector和Container。一个Service只有一个Container,但是可以有多个Connectors,这是因为一个服务可以有多个连接,如同时提供Http和Https链接。

3.Connector

Connector用于接收请求并将接收的请求封装为Request和Response来具体处理,Request和Response封装完之后再交由Container进行处理,Container处理完请求之后再返回给Connector,最后在由Connector通过Socket将处理的结果返回给客户端,这样整个请求的就处理完了!
在这里插入图片描述

4.Container

Container : 用于封装和管理Servlet,和处理Request请求;Tomcat 设计了 4 种容器,分别是 Engine、Host、Context 和 Wrapper。这 4 种容器不是平行关系,而是父子关系。

  • Engine:引擎,Servlet 的顶层容器,用来管理多个虚拟站点,一个 Service 最多只能有一个 Engine;
  • Host:虚拟主机,负责 web 应用的部署和 Context 的创建。可以给 Tomcat 配置多个虚拟主机地址,而一个虚拟主机下可以部署多个 Web 应用程序;可以给每个Host配置一个域名
  • Context:Web 应用上下文,包含多个 Wrapper,负责 web 配置的解析、管理所有的 Web 资源。一个Context对应一个 Web 应用程序。
  • Wrapper:表示一个 Servlet,最底层的容器,是对 Servlet 的封装,负责 Servlet 实例的创建、执行和销毁。

当我们把项目部署到Tomcat容器中,通过浏览器访问Tomcat端口即可访问到项目,那么Tomcat是如何去执行我们的代码的呢?这里我画了一个流程图

在这里插入图片描述

  1. 客户端发起Http请求,Tomcat根据协议和端口号找到对应的Connector连接器
  2. Connector连接器请求Container容器,再通过域名匹配对应的Host主机
  3. 然后再根据URL中的Path匹配到Context组件,再根据URL找到对应的Wrapper
  4. 然后执行Wrapper中的Servlet拿到结果返回

三.深入探究-Tomcat的执行原理

1.连接器Connector详解

Connector用于接受请求并将请求封装成Request和Response,然后交给Container进行处理,Container处理完之后在交给Connector返回给客户端。因此Connector需要处理如下几个事情

  1. 监听端口,处理客户端请求,读取请求网络字节流
  2. 根据协议(Http/AJP)解析字节流,封装Tomcat的Request对象,并将Tomcat Request转换为 ServletRequest对象
  3. 调用Servlet得到ServletResponse,把ServletResponse转换为TomcatResponse
  4. 把TomcatResponse转换为网络字节流,响应给客户端

在这里插入图片描述

Connector中使用ProtocolHandler来封装不同的IO模式和应用协议组合,例如NIO模式和HTTP1.1协议对应的就是Http11NioProtocol类,BIO采用的是Http11Protocol。在配置文件中配置Connector的protocol属性值Http11NioProtocol即使其成为支持NIO和HTTP1.1的连接器。Tomcat8默认是NIO。

<Connector port="8080" executor="tomcatThreadPool" protocol="org.apache.coyote.http11.Http11NioProtocol" 
redirectPort="8443" enableLookups="false"  URIEncoding="UTF-8" maxKeepAliveRequests="500" />

ProtocolHandler包含了Endpoint、Processor。EndPoint类负责Socket请求的收发,Processor类负责从Socket中解析出对应协议格式的数据并封装为Request 。然后通过适配器Adapter将Request转换为标准的ServletRequest传递给Servlet容器进行处理。这样每个模块只负责必要的部分,实现功能模块的高内聚和低耦合

2.BIO容器JIoEndpoint详解

EndPoint 支持BIO和NIO实现,对于BIO模式采用JIoEndpoint处理,NIO则采用NIoEndpoint。他们都维护了一个Acceptor来接受Socket,BIO的Acceptor是从线程池中找出空闲的线程处理socket,如果线程池没有空闲线程,则Acceptor将阻塞。
在这里插入图片描述

BIO的Connector中使用Http11Protocol处理请求,其中通过JIoEndpoint对象及Http11ConnectionHandler对象来配合完成

Http11Protocol#JIoEndpoint

下面是Connector#startInternal 调用Http11Protocol#starter方法最终会调用到 JIoEndpoint#start方法
在这里插入图片描述
下面是 org.apache.tomcat.util.net.JIoEndpoint#startInternal 方法源码,方法中会创建好线程池,并初始化链接数量,启动acceptor的线程

    public void startInternal() throws Exception {

        if (!running) {
            running = true;
            paused = false;

            // Create worker collection
            //创建线程池 Executor
            if (getExecutor() == null) {
                createExecutor();
            }
			//初始化链接数量,Tomcat通过 connectionLimitLatch来控制连接数
            initializeConnectionLatch();
			//开始 Acceptor 的线程,Acceptor 本身是一个runnable对象
            startAcceptorThreads();

            // Start async timeout thread
            Thread timeoutThread = new Thread(new AsyncTimeout(),
                    getName() + "-AsyncTimeout");
            timeoutThread.setPriority(threadPriority);
            timeoutThread.setDaemon(true);
            timeoutThread.start();
        }
    }
JIoEndpoint#Acceptor

JIoEndpoint容器由Acceptor来接受Socket请求,然后交给工作线程Worker去处理,Accepet和Worker都用到了线程池。Acceptor是接收socket连接,然后从Worker线程池中找出空闲的线程处理Socket,如果worker线程池没有空闲线程,则Acceptor将阻塞。Worker线程拿到socket后,就从Http11Processor对象池中获取Http11Processor对象,进一步处理。Worker线程池可以自定义。下面是 org.apache.tomcat.util.net.JIoEndpoint.Acceptor 源码

protected class Acceptor extends AbstractEndpoint.Acceptor 
	try {
        //if we have reached max connections, wait
        //计数或者等待连接,如果我们已达到最大连接数,请等待
        countUpOrAwaitConnection();

        Socket socket = null;
        try {
            // Accept the next incoming connection from the server
            // 接受Socket
            socket = serverSocketFactory.acceptSocket(serverSocket);
        } catch (IOException ioe) {
			...
        }
        // Successful accept, reset the error delay
        errorDelay = 0;

        // Configure the socket
        if (running && !paused && setSocketOptions(socket)) {
            // Hand this socket off to an appropriate processor
            //[重要]调用处理器处理Socket
            if (!processSocket(socket)) {
                countDownConnection();
                // Close socket right away
                closeSocket(socket);
            }
        } else {
            countDownConnection();
            // Close socket right away
            closeSocket(socket);
        }
    } catch (IOException x) {

下面是 org.apache.tomcat.util.net.JIoEndpoint#processSocket 处理Socket的源码,方法中把Socket封装到 SocketWrapper对象中,然后把 SocketWrapper 交给 SocketProcessor去处理。而SocketProcessor本身是一个runnable,则由线程池去执行

    protected boolean processSocket(Socket socket) {
        // Process the request from this socket
        try {
        	//包装socket
            SocketWrapper<Socket> wrapper = new SocketWrapper<Socket>(socket);
            wrapper.setKeepAliveLeft(getMaxKeepAliveRequests());
            wrapper.setSecure(isSSLEnabled());
            // During shutdown, executor may be null - avoid NPE
            if (!running) {
                return false;
            }
            //调用 线程池 来执行 : SocketProcessor
            getExecutor().execute(new SocketProcessor(wrapper));
   			...省略...
        return true;
    }

下面是 SocketProcessor#run方法,方法中会调用Http11ConnectionHandler#process 来处理Socket的OPEN_READ事件

protected class SocketProcessor implements Runnable {
     public void run() {
         if ((state != SocketState.CLOSED)) {
              if (status == null) {
              	//调用 Http11ConnectionHandler 处理socket
                  state = handler.process(socket, SocketStatus.OPEN_READ);
              } else {
                  state = handler.process(socket,status);
              }
          }
          ...省略...

在Http11ConnectionHandler#process 方法中从对象池中找到 Http11Processor 去处理Socket

	 public SocketState process(SocketWrapper<S> wrapper,
                SocketStatus status) {
				//从对象池中拿到Processor
                Processor<S> processor = connections.get(socket);
                if (processor == null) {
                	//创建Processor
                    processor = createProcessor();
                }
  				if (state == SocketState.OPEN) {

                     getProtocol().endpoint.removeWaitingRequest(wrapper);
                    //调用processor.process 去处理Socket
                      state = processor.process(wrapper);
                  }
	}
}
Http11ConnectionHandler

Http11ConnectionHandler 维护了一个Http11Processor对象池(ConcurrentHashMap),Http11Processor对象会解析Socket中的http request并封装到org.apache.coyote.Request对象中,随后调用CoyoteAdapter继续完成解析,构造org.apache.catalina.connector.Request和Response对象,并且通过 执行链 的方式将请求转发发给对应的Servlet容器,下面是 Http11Processor#process的源码

public abstract class AbstractHttp11Processor<S> extends AbstractProcessor<S> {
    //适配器
    protected Adapter adapter;
	//请求
    protected Request request;
    //响应
    protected Response response;
    //Socket对象
  	protected SocketWrapper<S> socketWrapper = null;
	public SocketState process(SocketWrapper<S> socketWrapper)
		//获取到请求信息
		RequestInfo rp = request.getRequestProcessor();
		//设置Socket
        setSocketWrapper(socketWrapper);
        //拿到输入输出流对象到 Buffer对象中
        getInputBuffer().init(socketWrapper, endpoint);
        getOutputBuffer().init(socketWrapper, endpoint);
        //设置协议,比如:http 1.1
        prepareRequestProtocol();
		//准备请求,设置请求头信息
		prepareRequest();
		...省略代码...
		//通过适配器CoyoteAdapter调用Servlet
		adapter.service(request, response);
	
	}

}

下面是CoyoteAdapter#service的源码,它通过 connector.getService().getContainer().getPipeline().getFirst().invoke( request, response); 来调用后续的容器对象。在这个执行链调用的过程中会把HttpServletRequest , Session等都准备好,然后通过path映射到对应的Servlet,执行其service方法

 public void service(org.apache.coyote.Request req, org.apache.coyote.Response res){

 // Calling the container : 调用容器
 // #StandardService -> StandardEngine -> StandardPipeline -> StandardContextValve -> 
  connector.getService().getContainer().getPipeline().getFirst().invoke(
              request, response);
}
 

对于BIO的connector,即同步阻塞,tomcat服务器实现模式为一个连接一个Worker线程,即客户端有连接时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情(比如后续的请求并没有到来,或者请求完毕连接还没有释放)会造成不必要的线程开销,非常消耗资源。BIO的connector在最新tomcat8.5及其之后的版本已经移除支持了。

3.NIO容器NIoEndpoint详解

NIoEndpoint#Acceptor

而对于NIO而言,在NIoEndpoint中Acceptor是吧Socket封装到NioChannel通道中,再基于Channel封装PollerEvent事件对象,然后再把PollerEvent放入NIoEndpoint维护的一个Poller中的一个SynchronizedQueue< PollerEvent > 队列中。下面是 org.apache.tomcat.util.net.Acceptor#run 的源码

public class Acceptor<U> implements Runnable {

	public void run() {
		...省略...
              // Accept the next incoming connection from the server
             // 接受 : socket 
              socket = endpoint.serverSocketAccept();
			  if (!stopCalled && !endpoint.isPaused()) {
                        // setSocketOptions() will hand the socket off to
                        // an appropriate processor if successful
                        //对Socket做配置,会把socket交给processor去处理
                        if (!endpoint.setSocketOptions(socket)) {
                            endpoint.closeSocket(socket);
                        }
                    } else {
                        endpoint.destroySocket(socket);
                    }
                    ...省略...

	}


}

下面是 org.apache.tomcat.util.net.NioEndpoint#setSocketOptions 源码,方法中会拿到封装的有Socket的NioChannel ,然后包装到NioSocketWrapper ,设置非阻塞模式后,会把NioSocketWrapper 注册到 Poller 中

protected boolean setSocketOptions(SocketChannel socket) {
        NioSocketWrapper socketWrapper = null;
        try {
            // Allocate channel and wrapper
            NioChannel channel = null;
            if (nioChannels != null) {
            	//从NioChannel栈中 中取出NioChannel 
                channel = nioChannels.pop();
            }
            if (channel == null) {
            	...
                if (isSSLEnabled()) {
                    channel = new SecureNioChannel(bufhandler, this);
                } else {
                    channel = new NioChannel(bufhandler);
                }
            }
            //把NioChannel包装到 NioSocketWrapper 
            NioSocketWrapper newWrapper = new NioSocketWrapper(channel, this);
            channel.reset(socket, newWrapper);
            //以socket为key,把NioSocketWrapper 放入一个ConcurrentHashMap中
            connections.put(socket, newWrapper);
            socketWrapper = newWrapper;

            // Set socket properties
            // Disable blocking, polling will be used
            //设置socket 非阻塞模式
            socket.configureBlocking(false);
			...
			//把Socket注册到Poller中
            poller.register(socketWrapper);
            return true;
        } catch (Throwable t) {
 			...
        }
        // Tell to close the socket if needed
        return false;
    }

下面是 org.apache.tomcat.util.net.NioEndpoint.Poller代码,里面维护了一个PollerEvent,NioSocketWrapper会被加入队列,在Poller中还维护了一个Selector选择器用来轮询socket事件

NioEndpoint#Poller
public class Poller implements Runnable {
		//事件选择器
        private Selector selector;
        //事件队列
        private final SynchronizedQueue<PollerEvent> events =
                new SynchronizedQueue<>();
	...
	 private void addEvent(PollerEvent event) {
        events.offer(event);
          if (wakeupCounter.incrementAndGet() == 0) {
              selector.wakeup();
          }
      }
	//注册
	public void register(final NioSocketWrapper socketWrapper) {
            socketWrapper.interestOps(SelectionKey.OP_READ);//this is what OP_REGISTER turns into.
            //把NioSocketWrapper 封装到PollerEvent 中
            PollerEvent pollerEvent = createPollerEvent(socketWrapper, OP_REGISTER);
            //把 PollerEvent 添加到 SynchronizedQueue 队列中
            addEvent(pollerEvent);
        }

Poller#run行中从SynchronizedQueue队列中取出NioChannel后注册到Selector中,然后通过Selector事件轮询拿到可读的(OP_READ)Socket,并从Worker线程池中拿到可用的Worker线程,然后将socket传递给Worker。

   public class Poller implements Runnable {
    	private volatile int keyCount = 0;
    	
		public void run() {
			//拿到key的数量,底层会调用操作系统的poll方法
			keyCount = selector.selectNow();
			...省略...
			//通过选择器selector拿到keys,
			Iterator<SelectionKey> iterator =
                    keyCount > 0 ? selector.selectedKeys().iterator() : null;
                // Walk through the collection of ready keys and dispatch
                // any active event.
                //对事件进行轮询
                while (iterator != null && iterator.hasNext()) {
                    SelectionKey sk = iterator.next();
                    iterator.remove();
                    NioSocketWrapper socketWrapper = (NioSocketWrapper) sk.attachment();
                    // Attachment may be null if another thread has called
                    // cancelledKey()
                    if (socketWrapper != null) {
                    	//处理事件
                        processKey(sk, socketWrapper);
                    }
                }
		}

   }

下面是 org.apache.tomcat.util.net.NioEndpoint.Poller#processKey Poller处理事件的代码逻辑,方法中会判断如果是isReadable || isWritable 就会父类的 org.apache.tomcat.util.net.AbstractEndpoint#processSocket方法来处理socket

protected void processKey(SelectionKey sk, NioSocketWrapper socketWrapper) {
	...省略...
	if (sk.isReadable() || sk.isWritable()) {
		
    // Read goes before write
          if (sk.isReadable()) {
              ...省略... 处理读事件
              processSocket(socketWrapper, SocketEvent.OPEN_READ, true))
          }
           if (!closeSocket && sk.isWritable()) {
           		...省略... 处理写事件
               processSocket(socketWrapper, SocketEvent.OPEN_WRITE, true)
           }
	
	}
}

下面是org.apache.tomcat.util.net.AbstractEndpoint#processSocket 处理socket的逻辑,方法中会根据 SocketEvent 和 socketWrapper 创建 SocketProcessor ,然后通过Executor线程池去执行SocketProcessor

public boolean processSocket(SocketWrapperBase<S> socketWrapper,
            SocketEvent event, boolean dispatch) {
        try {
            if (socketWrapper == null) {
                return false;
            }
            SocketProcessorBase<S> sc = null;
            if (processorCache != null) {
                sc = processorCache.pop();
            }
            if (sc == null) {
            //创建SocketProcessor
                sc = createSocketProcessor(socketWrapper, event);
            } else {
                sc.reset(socketWrapper, event);
            }
            //拿到线程池
            Executor executor = getExecutor();
            if (dispatch && executor != null) {
            	//通过线程池去执行SocketProcessor
                executor.execute(sc);
            } else {
                sc.run();
            }
        } catch (RejectedExecutionException ree) {
            getLog().warn(sm.getString("endpoint.executor.fail", socketWrapper) , ree);
            return false;
        } catch (Throwable t) {
            ExceptionUtils.handleThrowable(t);
            // This means we got an OOM or similar creating a thread, or that
            // the pool and its queue are full
            getLog().error(sm.getString("endpoint.process.fail"), t);
            return false;
        }
        return true;
    }
SocketProcessor

将socket封装在SocketProcessor对象中。SocketProcessor 的 Run 方法会调用 Processor 组件去解析应用层协议,Processor 通过解析生成Request 对象后,会调用 Adapter 的 Service 方法。而这些工作都是通过线程池去完成的。
在这里插入图片描述

对于NIO模式需要系统学习之后才能看,可以看我另外一个系列的文章 《Netty入门到超神

4.总结BIO和NIO的区别

对于BIO的connector,即同步阻塞,tomcat服务器实现模式为一个连接一个Worker线程,即客户端有连接时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,非常消耗资源。BIO的connector在最新tomcat8.5及其之后的版本已经移除支持了。

对于NIO的connector,即同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器Selector上,多路复用器轮询到连接有I/O请求事件时才会启动一个Worker线程进行处理,请求处理完毕之后Worker线程马上释放。性能比较高。

5.Pipeline-Valve责任链

在Tomcat执行的过程中用到了责任链设计模式,比如:connector.getService().getContainer().getPipeline().getFirst().invoke( request, response);连接器中的 Adapter 会调用容器的 Service 方法来执行Servlet,最先拿到请求的是 Engine 容器,Engine 容器对请求做一些处理后,会把请求传给自己子容器 Host 继续处理,依次类推,最后这个请求会传给 Wrapper 容器,Wrapper 会调用最终的 Servlet 来处理。那么这个调用过程就采用了责任链模式。

在tomcat中通过 Pipeline 和 Valve来实现责任链,Pipeline源码如下

public interface Pipeline extends Contained {
    Valve getBasic();
    void setBasic(Valve valve);
	Valve[] getValves();
	void addValve(Valve valve);
	
	//获取第一个 Valve
	Valve getFirst();
}

Valve源码如下


public interface Valve {
	//获取下个Valve 
	Valve getNext();
	
    void setNext(Valve valve);
    void invoke(Request request, Response response)throws IOException, ServletException;
	boolean isAsyncSupported();
}

Tomcat 中 Container 容器有四个分别是 Engine、Host、Context 和 Wrapper。这 4 种容器不是平行关系,而是父子关系。
在这里插入图片描述
这四个容器分别对应了四个类

  • public class StandardEngine extends ContainerBase implements Engine
  • public class StandardHost extends ContainerBase implements Host
  • public class StandardContext extends ContainerBase implements Context
  • public class StandardWrapper extends ContainerBase implements ServletConfig, Wrapper

这四个类都继承了ContainerBase ,其中维护了一个Pipeline (管道),Pipeline 中维护了 Valve (阀门)链表,Valve 可以插入到 Pipeline 中,通过Pipeline#getFirst 可以获取到第一个Valve ,Valve#getNext方法可以下一个Valve,这样就形成一个链式结构 ,Basic Valve 处于 Valve 链表的末端,它是 Pipeline 中必不可少的一个Valve,负责调用下层容器的 Pipeline 里的第一个 Valve。
在这里插入图片描述
整个调用过程由连接器中的 Adapter 触发的,它会调用 Engine 的第一个 Valve

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

Wrapper 容器的最后一个 Valve 会创建一个 Filter 链,并调用 doFilter() 方法,最终会调到 Servlet的 service 方法。

filterChain.doFilter(request.getRequest(), response.getResponse())

责任链的好处:

在一个比较复杂的大型系统中,如果一个对象或数据流需要进行繁杂的逻辑处理,我们可以选择在一个大的组件中直接处理这些繁杂的业务逻辑, 这个方式虽然达到目的,但扩展性和可重用性较差, 因为可能牵一发而动全身。更好的解决方案是采用管道机制,用一条管道把多个对象(阀门部件)连接起来,整体看起来就像若干个阀门嵌套在管道中一样,而处理逻辑放在阀门上。

文章就写到这里吧,如果对你有所帮助请点赞收藏哦!!!

  • 27
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

墨家巨子@俏如来

你的鼓励是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值