Tomcat是如何处理多个请求的

咱们以排队买票为例子,说说三种方案:java

一、火车站只提供一个窗口,全部的人都必须排队等待。你们都知道这是多么糟糕的体验,后来的人必须等前面的人买完票才能进入申请购票,更糟糕的是中间还会发生一些小意外,好比机器卡了,某个乘客由于一些小矛盾与售票员发生了激烈争执呀等等。从程序角度上说,就是server只用一个线程来处理全部请求任务,这不能充分使用服务器资源,是很是低效的一种策略,而前面的请求任务可能在链接数据库,读取文件时发生长时间阻塞,致使后来的请求进入长时间的等待状态。数据库

二、火车站为每个购票用户配备一个临时售票员,这刚开始是很是高效的,但随着购票用户的增长,整个火车站都将被挤爆。从程序角度说,就是每来一个请求,就建立一个线程处理,这样多个请求就能够被并行处理,大大提升的资源使用率和任务处理效率,可是建立线程自己就是消耗资源的,而大量空闲线程将占用了内存(超过上限后会报OutOfMemory异常),也使得cpu在频繁的上下文切换中形成了性能损耗。apache

三、火车站增长多个售票窗口,乘客仍然要排队,但处理效率更高了,哪一个窗口闲了,就处理新的购票申请。这相似于tomcat中的线程池,线程池是用来管理工做线程的,通常和队列配合使用,他对线程进行重复使用,减小了频繁建立线程的消耗,同时能够对线程数量进行控制,在不超过负载的前提下,充分使用内存和cpu资源。tomcat

Tomcat建立线程池的方法在AbstractEndpoint类中,它有三个子类,分别用来实现tomcat connector 的三种运行模式:BIO,NIO和APR,在此咱们仅针对BIO的运行模式进行分析。服务器

该类有一个建立线程池的方法:并发

public void createExecutor() {

 internalExecutor = true;

  TaskQueue taskqueue = new TaskQueue();

  TaskThreadFactory tf = new TaskThreadFactory(getName() + "-exec-", daemon, getThreadPriority());

 executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), 60, TimeUnit.SECONDS,taskqueue, tf);

 taskqueue.setParent( (ThreadPoolExecutor) executor);

 }

说明一点,这个线程池主要是处理请求任务的,而对请求的接受主要由Acceptor(实现Runnable)完成,其线程数量由acceptorThreadCount指定,默认值是1。less

咱们再来看下ThreadPoolExecutor构造函数:socket

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) 

corePoolSize - 池中所保存的线程数,包括空闲线程。

maximumPoolSize - 池中容许的最大线程数。

keepAliveTime - 当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。

unit - keepAliveTime 参数的时间单位。

workQueue - 执行前用于保持任务的队列。此队列仅保持由 execute 方法提交的 Runnable 任务。

threadFactory - 执行程序建立新线程时使用的工厂。

通常规则是当运行线程少于corePoolSize,Executor将建立新线程处理任务,若是等于或多于corePoolSize,则请求将加入队列,而不建立新线程,若是没法加入队列,则建立新线程,直至大于maximumPoolSize ,任务被拒绝ide

以上规则结合ThreadPoolExecutor execute方法源码会更容易理解:函数

int c = ctl.get();
 if (workerCountOf(c) < corePoolSize) {
  if (addWorker(command, true))
  return;
   c = ctl.get();
  }
/*
根据taskqueue的offer方法,若现有线程数量小于maxThreads,workQueue.offer(command)返回false,不放入队列,建立一个新线程处理请求任务(即addWorker(command,flase))
*/
 if (isRunning(c) && workQueue.offer(command)) {
  int recheck = ctl.get();
   if (!isRunning(recheck) && remove(command))
   reject(command);
  else if (workerCountOf(recheck) == 0)
   addWorker(null, false);
  } else if (!addWorker(command, false))
  reject(command);

maxThreads默认值是200,而TaskQueue对LinkedBlockingQueue的offer()方法进行了覆盖,添加了一些新的规则:

@Override
 public boolean offer(Runnable o) {
 // we can't do any checks
 if (parent == null)
 return super.offer(o);
 // we are maxed out on threads, simply queue the object
 if (parent.getPoolSize() == parent.getMaximumPoolSize())
 return super.offer(o);
 // we have idle threads, just add it to the queue
 if (parent.getSubmittedCount() < (parent.getPoolSize()))
 return super.offer(o);
 // if we have less threads than maximum force creation of a new thread
 if (parent.getPoolSize() < parent.getMaximumPoolSize())
 return false;
 // if we reached here, we need to add it to the queue
 return super.offer(o);
}

在加入队列过程当中,若发现现有线程数小于最大线程数且没有空闲线程,它会建立新的线程。该队列默认是一个无界队列,现有线程数大于等于最大线程数时,请求任务会加入队列等待。

并且,tomcat建立线程线程数还受maxConnections限制,代码以下:

// 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) {
 countDownConnection();
 // Introduce delay if necessary
 errorDelay = handleExceptionWithDelay(errorDelay);
 // re-throw
 throw ioe;
  }

当链接达到maxConnections时,请求不会被socket接受,而是进入TCP的彻底链接队列中,队列的大小由acceptCount值决定,默认是100.

因而tomcat处理请求的过程即是:Acceptor接收一个请求,若现有线程数量小于maxThreads且没有空闲线程,则建立一个新线程处理请求任务,若超过maxThreads(BIO模式下,maxConnections默认值等同于maxThreads),则放入TCP彻底链接队列中(注意,不是线程池中的队列),当队列大于acceptCount值时,则报“connection refused”错误。

虽然线程池技术提升了性能,缩短了请求响应时间,同时防止了突发性大量请求引发的资源耗尽,但其本质上仍是一个线程处理一个请求,线程池技术结合NIO技术,让少许线程处理大量请求,将极大得提升并发能力,在tomcat6之后,已经实现了这一技术,只要将server.xml配置改为以下便可:

<Connector port="8080" protocol="org.apache.coyote.http11.Http11NioProtocol" 

         connectionTimeout="20000" redirectPort="8443"/>

有关ThreadPoolExecutor的源码解读和Nio的内容,之后还会详细讲解。

 

转载自:https://cloud.tencent.com/developer/article/1033735

相关文章
相关标签/搜索