前言
在使用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
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:bioorg.apache.coyote.http11.http11nioprotocol:nioorg.apache.coyote.http11.http11nio2protocol:nio2org.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,處理流程如下圖所示。
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的配置舉例如下:
<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:線程池中保持的最小線程數,最小值是25maxidletime:線程空閑的大時間,當空閑超過該值時關閉線程(除非線程數小于minsparethreads),單位是ms,默認值60000(1分鐘)daemon:是否后臺線程,默認值truethreadpriority:線程優先級,默認值5nameprefix:線程名字的前綴,線程池中線程名字為:nameprefix+線程編號四、查看當前狀態
上面介紹了tomcat連接數、線程數的概念以及如何設置,下面說明如何查看服務器中的連接數和線程數。
查看服務器的狀態,大致分為兩種方案:(1)使用現成的工具,(2)直接使用linux的命令查看。
現成的工具,如jdk自帶的jconsole工具可以方便的查看線程信息(此外還可以查看cpu、內存、類、jvm基本信息等),tomcat自帶的manager,收費工具new relic等。下圖是jconsole查看線程信息的界面:
下面說一下如何通過linux命令行,查看服務器中的連接數和線程數。
1、連接數
假設tomcat接收http請求的端口是8083,則可以使用如下語句查看連接情況:
netstat –nat | grep 8083結果如下所示:
可以看出,有一個連接處于listen狀態,監聽請求;除此之外,還有4個已經建立的連接(established)和2個等待關閉的連接(close_wait)。
2、線程
ps命令可以查看進程狀態,如執行如下命令:
ps –e | grep java結果如下圖:
可以看到,只打印了一個進程的信息;27989是線程id,java是指執行的java命令。這是因為啟動一個tomcat,內部所有的工作都在這一個進程里完成,包括主線程、垃圾回收線程、acceptor線程、請求處理線程等等。
通過如下命令,可以看到該進程內有多少個線程;其中,nlwp含義是number of light-weight process。
ps –o nlwp 27989
可以看到,該進程內部有73個線程;但是73并沒有排除處于idle狀態的線程。要想獲得真正在running的線程數量,可以通過以下語句完成:
ps -elo pid ,stat | grep 27989 | grep running | wc -l其中ps -elo pid ,stat可以找出所有線程,并打印其所在的進程號和線程當前的狀態;兩個grep命令分別篩選進程號和線程狀態;wc統計個數。其中,ps -elo pid ,stat | grep 27989輸出的結果如下:
圖中只截圖了部分結果;sl表示大多數線程都處于空閑狀態。
東莞到隴南物流專線濟南到南寧物流專線杭州到德州物流專線深圳到彌勒物流專線自定義網頁設計與網站模板–尋找合適的網站泰州到汝州物流專線做好個人網站建設與運營管理的五大方面金華到東方物流專線