詳解tomcat的連接數與線程池

前言

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

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

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

根據協議的不同,Connector可以分爲HTTP Connector、AJP Connector等,本文只討論HTTP Connector。

目錄

一、Nio、Bio、APR

    1、Connector的protocol

    2、如何選擇protocol

    3、BIO/NIO有何不同

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

    1、acceptCount

    2、maxConnections

    3、maxThreads

    4、參數設置

三、線程池Executor

四、查看當前狀態

    1、連接數

    2、線程

參考文獻

一、Nio、Bio、APR

1、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等包。

2、如何指定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)。

3、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數目遠大於最大線程數,併發性能大大提高。

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

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

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

1、acceptCount

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

2、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。

3、maxThreads

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

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

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

4、參數設置

(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。

三、線程池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+線程編號

四、查看當前狀態

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

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

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

 

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

1、連接數

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

1

netstat –nat | grep 8083

結果如下所示:

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

2、線程

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

 

轉自: https://www.cnblogs.com/kismetv/p/7806063.html#t3

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章