詳解tomcat的鏈接數與線程池

前言

在使用tomcat時,常常會遇到鏈接數、線程數之類的配置問題,要真正理解這些概念,必須先了解Tomcat的鏈接器(Connector)。html

在前面的文章 詳解Tomcat配置文件server.xml 中寫到過:Connector的主要功能,是接收鏈接請求,建立Request和Response對象用於和請求端交換數據;而後分配線程讓Engine(也就是Servlet容器)來處理這個請求,並把產生的Request和Response對象傳給Engine。當Engine處理完請求後,也會經過Connector將響應返回給客戶端。java

能夠說,Servlet容器處理請求,是須要Connector進行調度和控制的,Connector是Tomcat處理請求的主幹,所以Connector的配置和使用對Tomcat的性能有着重要的影響。這篇文章將從Connector入手,討論一些與Connector有關的重要問題,包括NIO/BIO模式、線程池、鏈接數等。數據庫

根據協議的不一樣,Connector能夠分爲HTTP Connector、AJP Connector等,本文只討論HTTP Connector。apache

目錄

1、Nio、Bio、APRwindows

    一、Connector的protocol
tomcat

    二、如何選擇protocol服務器

    三、BIO/NIO有何不一樣架構

2、3個參數:acceptCount、maxConnections、maxThreads併發

    一、acceptCountsocket

    二、maxConnections

    三、maxThreads

    四、參數設置

3、線程池Executor

4、查看當前狀態

    一、鏈接數

    二、線程

參考文獻

1、Nio、Bio、APR

一、Connector的protocol

Connector在處理HTTP請求時,會使用不一樣的protocol。不一樣的Tomcat版本支持的protocol不一樣,其中最典型的protocol包括BIO、NIO和APR(Tomcat7中支持這3種,Tomcat8增長了對NIO2的支持,而到了Tomcat8.5和Tomcat9.0,則去掉了對BIO的支持)。

BIO是Blocking IO,顧名思義是阻塞的IO;NIO是Non-blocking IO,則是非阻塞的IO。而APR是Apache Portable Runtime,是Apache可移植運行庫,利用本地庫能夠實現高可擴展性、高性能;Apr是在Tomcat上運行高併發應用的首選模式,可是須要安裝apr、apr-utils、tomcat-native等包。

二、如何指定protocol

Connector使用哪一種protocol,能夠經過<connector>元素中的protocol屬性進行指定,也可使用默認值。

指定的protocol取值及對應的協議以下:

  • HTTP/1.1:默認值,使用的協議與Tomcat版本有關
  • org.apache.coyote.http11.Http11Protocol:BIO
  • org.apache.coyote.http11.Http11NioProtocol:NIO
  • org.apache.coyote.http11.Http11Nio2Protocol:NIO2
  • org.apache.coyote.http11.Http11AprProtocol:APR

若是沒有指定protocol,則使用默認值HTTP/1.1,其含義以下:在Tomcat7中,自動選取使用BIO或APR(若是找到APR須要的本地庫,則使用APR,不然使用BIO);在Tomcat8中,自動選取使用NIO或APR(若是找到APR須要的本地庫,則使用APR,不然使用NIO)。

三、BIO/NIO有何不一樣

不管是BIO,仍是NIO,Connector處理請求的大體流程是同樣的:

在accept隊列中接收鏈接(當客戶端向服務器發送請求時,若是客戶端與OS完成三次握手創建了鏈接,則OS將該鏈接放入accept隊列);在鏈接中獲取請求的數據,生成request;調用servlet容器處理請求;返回response爲了便於後面的說明,首先明確一下鏈接與請求的關係:鏈接是TCP層面的(傳輸層),對應socket;請求是HTTP層面的(應用層),必須依賴於TCP的鏈接實現;一個TCP鏈接中可能傳輸多個HTTP請求。

在BIO實現的Connector中,處理請求的主要實體是JIoEndpoint對象。JIoEndpoint維護了Acceptor和Worker:Acceptor接收socket,而後從Worker線程池中找出空閒的線程處理socket,若是worker線程池沒有空閒線程,則Acceptor將阻塞。其中Worker是Tomcat自帶的線程池,若是經過<Executor>配置了其餘線程池,原理與Worker相似。

在NIO實現的Connector中,處理請求的主要實體是NIoEndpoint對象。NIoEndpoint中除了包含Acceptor和Worker外,仍是用了Poller,處理流程以下圖所示(圖片來源:http://gearever.iteye.com/blog/1844203)。

 

Acceptor接收socket後,不是直接使用Worker中的線程處理請求,而是先將請求發送給了Poller,而Poller是實現NIO的關鍵。Acceptor向Poller發送請求經過隊列實現,使用了典型的生產者-消費者模式。在Poller中,維護了一個Selector對象;當Poller從隊列中取出socket後,註冊到該Selector中;而後經過遍歷Selector,找出其中可讀的socket,並使用Worker中的線程處理相應請求。與BIO相似,Worker也能夠被自定義的線程池代替。

經過上述過程能夠看出,在NIoEndpoint處理請求的過程當中,不管是Acceptor接收socket,仍是線程處理請求,使用的仍然是阻塞方式;但在「讀取socket並交給Worker中的線程」的這個過程當中,使用非阻塞的NIO實現,這是NIO模式與BIO模式的最主要區別(其餘區別對性能影響較小,暫時略去不提)。而這個區別,在併發量較大的情形下能夠帶來Tomcat效率的顯著提高:

目前大多數HTTP請求使用的是長鏈接(HTTP/1.1默認keep-alive爲true),而長鏈接意味着,一個TCP的socket在當前請求結束後,若是沒有新的請求到來,socket不會立馬釋放,而是等timeout後再釋放。若是使用BIO,「讀取socket並交給Worker中的線程」這個過程是阻塞的,也就意味着在socket等待下一個請求或等待釋放的過程當中,處理這個socket的工做線程會一直被佔用,沒法釋放;所以Tomcat能夠同時處理的socket數目不能超過最大線程數,性能受到了極大限制。而使用NIO,「讀取socket並交給Worker中的線程」這個過程是非阻塞的,當socket在等待下一個請求或等待釋放時,並不會佔用工做線程,所以Tomcat能夠同時處理的socket數目遠大於最大線程數,併發性能大大提升。

2、3個參數:acceptCount、maxConnections、maxThreads

再回顧一下Tomcat處理請求的過程:在accept隊列中接收鏈接(當客戶端向服務器發送請求時,若是客戶端與OS完成三次握手創建了鏈接,則OS將該鏈接放入accept隊列);在鏈接中獲取請求的數據,生成request;調用servlet容器處理請求;返回response

相對應的,Connector中的幾個參數功能以下:

一、acceptCount

accept隊列的長度;當accept隊列中鏈接的個數達到acceptCount時,隊列滿,進來的請求一概被拒絕。默認值是100。

二、maxConnections

Tomcat在任意時刻接收和處理的最大鏈接數。當Tomcat接收的鏈接數達到maxConnections時,Acceptor線程不會讀取accept隊列中的鏈接;這時accept隊列中的線程會一直阻塞着,直到Tomcat接收的鏈接數小於maxConnections。若是設置爲-1,則鏈接數不受限制。

默認值與鏈接器使用的協議有關:NIO的默認值是10000,APR/native的默認值是8192,而BIO的默認值爲maxThreads(若是配置了Executor,則默認值是Executor的maxThreads)。

在windows下,APR/native的maxConnections值會自動調整爲設置值如下最大的1024的整數倍;如設置爲2000,則最大值實際是1024。

三、maxThreads

請求處理線程的最大數量。默認值是200(Tomcat7和8都是的)。若是該Connector綁定了Executor,這個值會被忽略,由於該Connector將使用綁定的Executor,而不是內置的線程池來執行任務。

maxThreads規定的是最大的線程數目,並非實際running的CPU數量;實際上,maxThreads的大小比CPU核心數量要大得多。這是由於,處理請求的線程真正用於計算的時間可能不多,大多數時間可能在阻塞,如等待數據庫返回數據、等待硬盤讀寫數據等。所以,在某一時刻,只有少數的線程真正的在使用物理CPU,大多數線程都在等待;所以線程數遠大於物理核心數纔是合理的。

換句話說,Tomcat經過使用比CPU核心數量多得多的線程數,可使CPU忙碌起來,大大提升CPU的利用率。

四、參數設置

(1)maxThreads的設置既與應用的特色有關,也與服務器的CPU核心數量有關。經過前面介紹能夠知道,maxThreads數量應該遠大於CPU核心數量;並且CPU核心數越大,maxThreads應該越大;應用中CPU越不密集(IO越密集),maxThreads應該越大,以便可以充分利用CPU。固然,maxThreads的值並非越大越好,若是maxThreads過大,那麼CPU會花費大量的時間用於線程的切換,總體效率會下降。

(2)maxConnections的設置與Tomcat的運行模式有關。若是tomcat使用的是BIO,那麼maxConnections的值應該與maxThreads一致;若是tomcat使用的是NIO,那麼相似於Tomcat的默認值,maxConnections值應該遠大於maxThreads。

(3)經過前面的介紹能夠知道,雖然tomcat同時能夠處理的鏈接數目是maxConnections,但服務器中能夠同時接收的鏈接數爲maxConnections+acceptCount 。acceptCount的設置,與應用在鏈接太高狀況下但願作出什麼反應有關係。若是設置過大,後面進入的請求等待時間會很長;若是設置太小,後面進入的請求立馬返回connection refused。

3、線程池Executor

Executor元素表明Tomcat中的線程池,能夠由其餘組件共享使用;要使用該線程池,組件須要經過executor屬性指定該線程池。

Executor是Service元素的內嵌元素。通常來講,使用線程池的是Connector組件;爲了使Connector能使用線程池,Executor元素應該放在Connector前面。Executor與Connector的配置舉例以下:

?
1
2
<Executor name= "tomcatThreadPool" namePrefix = "catalina-exec-" maxThreads= "150" minSpareThreads= "4" />
<Connector executor= "tomcatThreadPool" port= "8080" protocol= "HTTP/1.1" connectionTimeout= "20000" redirectPort= "8443" acceptCount= "1000" />

Executor的主要屬性包括:

  • name:該線程池的標記
  • maxThreads:線程池中最大活躍線程數,默認值200(Tomcat7和8都是)
  • minSpareThreads:線程池中保持的最小線程數,最小值是25
  • maxIdleTime:線程空閒的最大時間,當空閒超過該值時關閉線程(除非線程數小於minSpareThreads),單位是ms,默認值60000(1分鐘)
  • daemon:是否後臺線程,默認值true
  • threadPriority:線程優先級,默認值5
  • namePrefix:線程名字的前綴,線程池中線程名字爲:namePrefix+線程編號

4、查看當前狀態

上面介紹了Tomcat鏈接數、線程數的概念以及如何設置,下面說明如何查看服務器中的鏈接數和線程數。

查看服務器的狀態,大體分爲兩種方案:(1)使用現成的工具,(2)直接使用Linux的命令查看。

現成的工具,如JDK自帶的jconsole工具能夠方便的查看線程信息(此外還能夠查看CPU、內存、類、JVM基本信息等),Tomcat自帶的manager,收費工具New Relic等。下圖是jconsole查看線程信息的界面:

 

下面說一下如何經過Linux命令行,查看服務器中的鏈接數和線程數。

一、鏈接數

假設Tomcat接收http請求的端口是8083,則可使用以下語句查看鏈接狀況:

?
1
netstat –nat | grep 8083

結果以下所示:

能夠看出,有一個鏈接處於listen狀態,監聽請求;除此以外,還有4個已經創建的鏈接(ESTABLISHED)和2個等待關閉的鏈接(CLOSE_WAIT)。

二、線程

ps命令能夠查看進程狀態,如執行以下命令:

?
1
ps –e | grep java

結果以下圖:

能夠看到,只打印了一個進程的信息;27989是線程id,java是指執行的java命令。這是由於啓動一個tomcat,內部全部的工做都在這一個進程裏完成,包括主線程、垃圾回收線程、Acceptor線程、請求處理線程等等。

經過以下命令,能夠看到該進程內有多少個線程;其中,nlwp含義是number of light-weight process。

?
1
ps –o nlwp 27989

能夠看到,該進程內部有73個線程;可是73並無排除處於idle狀態的線程。要想得到真正在running的線程數量,能夠經過如下語句完成:

?
1
ps -eLo pid ,stat | grep 27989 | grep running | wc -l

其中ps -eLo pid ,stat能夠找出全部線程,並打印其所在的進程號和線程當前的狀態;兩個grep命令分別篩選進程號和線程狀態;wc統計個數。其中,ps -eLo pid ,stat | grep 27989輸出的結果以下:

 圖中只截圖了部分結果;Sl表示大多數線程都處於空閒狀態。

參考文獻

Tomcat 7.0官方文檔

Tomcat 8.0官方文檔

Tomcat 8.5官方文檔

Tomcat maxThreads maxConnections acceptCount參數說明

tomcat架構分析(connector BIO 實現)

tomcat架構分析 (connector NIO 實現)

Why is the tomcat default thread pool size so large?

Howto find Tomcat current thread count

原文出處:http://www.cnblogs.com/kismetv/p/7806063.html

相關文章
相關標籤/搜索