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

相關文章
相關標籤/搜索