1. 程式人生 > >Tomcat BIO 、NIO 、APR

Tomcat BIO 、NIO 、APR

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

Q:如何指定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)。

Tomcat can use two different implementations of SSL:

  • the JSSE implementation provided as part of the Java runtime (since 1.4)
  • the APR implementation, which uses the OpenSSL engine by default.

The exact configuration details depend on which implementation is being used. If you configured Connector by specifying generic protocol="HTTP/1.1" then the implementation used by Tomcat is chosen automatically. If the installation uses APR – i.e. you have installed the Tomcat native library – then it will use the APR SSL implementation, otherwise it will use the Java JSSE implementation.

As configuration attributes for SSL support significantly differ between APR vs. JSSE implementations, it is recommended to avoid auto-selection of implementation. It is done by specifying a classname in the protocol attribute of the Connector.

To define a Java (JSSE) connector, regardless of whether the APR library is loaded or not, use one of the following:

<!-- Define a HTTP/1.1 Connector on port 8443, JSSE NIO implementation -->
<Connector protocol="org.apache.coyote.http11.Http11NioProtocol"
           port="8443" .../>

<!-- Define a HTTP/1.1 Connector on port 8443, JSSE BIO implementation -->
<Connector protocol="org.apache.coyote.http11.Http11Protocol"
           port="8443" .../>

Alternatively, to specify an APR connector (the APR library must be available) use:

<!-- Define a HTTP/1.1 Connector on port 8443, APR implementation -->
<Connector protocol="org.apache.coyote.http11.Http11AprProtocol"
           port="8443" .../>

If you are using APR, you have the option of configuring an alternative engine to OpenSSL.


<Listener className="org.apache.catalina.core.AprLifecycleListener"
          SSLEngine="someengine" SSLRandomSeed="somedevice" />

The default value is


<Listener className="org.apache.catalina.core.AprLifecycleListener"
          SSLEngine="on" SSLRandomSeed="builtin" />

So to use SSL under APR, make sure the SSLEngine attribute is set to something other than off. The default value is on and if you specify another value, it has to be a valid engine name.

SSLRandomSeed allows to specify a source of entropy. Productive system needs a reliable source of entropy but entropy may need a lot of time to be collected therefore test systems could use no blocking entropy sources like “/dev/urandom” that will allow quicker starts of Tomcat.

The final step is to configure the Connector in the $CATALINA_BASE/conf/server.xml file, where $CATALINA_BASE represents the base directory for the Tomcat instance. An example <Connector> element for an SSL connector is included in the default server.xml file installed with Tomcat. To configure an SSL connector that uses JSSE, you will need to remove the comments and edit it so it looks something like this:


<!-- Define a SSL Coyote HTTP/1.1 Connector on port 8443 -->
<Connector
           protocol="org.apache.coyote.http11.Http11NioProtocol"
           port="8443" maxThreads="200"
           scheme="https" secure="true" SSLEnabled="true"
           keystoreFile="${user.home}/.keystore" keystorePass="changeit"
           clientAuth="false" sslProtocol="TLS"/>

The APR connector uses different attributes for many SSL settings, particularly keys and certificates. An example of an APR configuration is:


<!-- Define a SSL Coyote HTTP/1.1 Connector on port 8443 -->
<Connector
           protocol="org.apache.coyote.http11.Http11AprProtocol"
           port="8443" maxThreads="200"
           scheme="https" secure="true" SSLEnabled="true"
           SSLCertificateFile="/usr/local/ssl/server.crt"
           SSLCertificateKeyFile="/usr/local/ssl/server.pem"
           SSLVerifyClient="optional" SSLProtocol="TLSv1+TLSv1.1+TLSv1.2"/>

The configuration options and information on which attributes are mandatory, are documented in the SSL Support section of the HTTP connector configuration reference. Make sure that you use the correct attributes for the connector you are using. The BIO and NIO connectors use JSSE whereas the APR/native connector uses APR.

The port attribute is the TCP/IP port number on which Tomcat will listen for secure connections. You can change this to any port number you wish (such as to the default port for https communications, which is 443). However, special setup (outside the scope of this document) is necessary to run Tomcat on port numbers lower than 1024 on many operating systems.

If you change the port number here, you should also change the value specified for the redirectPort attribute on the non-SSL connector. This allows Tomcat to automatically redirect users who attempt to access a page with a security constraint specifying that SSL is required, as required by the Servlet Specification.

After completing these configuration changes, you must restart Tomcat as you normally do, and you should be in business. You should be able to access any web application supported by Tomcat via SSL. For example, try:

https://localhost:8443/

and you should see the usual Tomcat splash page (unless you have modified the ROOT web application). If this does not work, the following section contains some troubleshooting tips.

Q:BIO/NIO有何不同

在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數目遠大於最大執行緒數,併發效能大大提高。

Tomcat處理請求的過程

在accept佇列中接收連線(當客戶端向伺服器傳送請求時,如果客戶端與OS完成三次握手建立了連線,則OS將該連線放入accept佇列);在連線中獲取請求的資料,生成request;呼叫servlet容器處理請求;返回response。

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。

執行緒池Executor

Executor元素代表Tomcat中的執行緒池,可以由其他元件共享使用;要使用該執行緒池,元件需要通過executor屬性指定該執行緒池。

Executor是Service元素的內嵌元素。一般來説,使用執行緒池的是Connector元件;為了使Connector能使用執行緒池,Executor元素應該放在Connector前面。Executor與Connector的配置舉例如下:

<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+執行緒編號

檢視當前狀態

如何檢視伺服器中的連線數和執行緒數。

檢視伺服器的狀態,大致分為兩種方案:(1)使用現成的工具,(2)直接使用Linux的命令檢視。

現成的工具,如JDK自帶的jconsole工具可以方便的檢視執行緒資訊(此外還可以檢視CPU、記憶體、類、JVM基本資訊等),Tomcat自帶的manager,收費工具New Relic等。下圖是jconsole檢視執行緒資訊的介面:

說明:jconsole 只能在有 GUI 的情況下正常執行,如果是 ssh 到遠端 server, jconsole 只能看到黑畫面…

連線數 (connection)

假設Tomcat接收http請求的埠是8083,則可以使用如下語句檢視連線情況:

netstat –nat | grep 8083

執行緒 (process)

ps命令可以檢視程序狀態,如執行如下命令:

ps –e | grep java

透過這個指令可以看到,java 執行緒(process) id.

number of light-weight process

通過如下命令,可以看到該程序內有多少個執行緒;其中,nlwp含義是number of light-weight process。

ps –o nlwp 1351

執行畫面:

真正在running的執行緒數量

ps -eLo pid,stat | grep 1353 | wc -l

執行畫面: