性能優(yōu)化|Tomcat 服務(wù)優(yōu)化

一、Tomcat工作原理

1. TCP的三次握手四次揮手

三次握手:

說明:

類比于A和B打電話:

A對B說:你好,我是A,你能聽到我說話嗎?

B對A說:嗯,我能聽到你說話

A對B說:好,那我們開始聊天吧

在服務(wù)器上使用如下命令能看到當(dāng)前服務(wù)器的連接情況

netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'

返回結(jié)果說明:

LAST_ACK 5 (正在等待處理的請求數(shù))

SYN_RECV 30

ESTABLISHED 1597 (正常數(shù)據(jù)傳輸狀態(tài))

FIN_WAIT1 51

FIN_WAIT2 504

TIME_WAIT 1057 (處理完畢,等待超時結(jié)束的請求數(shù))

四次揮手:

說明:

同樣用A和B打電話來說明:

A對B說:我說完了,我要掛電話了

B對A說:等一下,我還沒說完

B繼續(xù)對A說:我說完了,你可以掛電話了

A對B說:好,我掛電話了

其他參數(shù)說明:

CLOSED:無連接是活動的或正在進(jìn)行

LISTEN:服務(wù)器在等待進(jìn)入呼叫

SYN_RECV:一個連接請求已經(jīng)到達(dá),等待確認(rèn)

SYN_SENT:應(yīng)用已經(jīng)開始,打開一個連接

ESTABLISHED:正常數(shù)據(jù)傳輸狀態(tài)

FIN_WAIT1:應(yīng)用說它已經(jīng)完成

FIN_WAIT2:另一邊已同意釋放

ITMED_WAIT:等待所有分組死掉

CLOSING:兩邊同時嘗試關(guān)閉

TIME_WAIT:另一邊已初始化一個釋放

LAST_ACK:等待所有分組死掉

2. Tomcat內(nèi)部結(jié)構(gòu)

上圖說明:

?server:指的是整個應(yīng)用的上下文, 也是最頂層的容器,tomcat中所有的東西都在這個server里邊。

?service:指的是一個服務(wù),主要的功能是把connector組件和engine組織起來,使得通過connector組件與整個容器通訊的應(yīng)用可以使用engine提供的服務(wù)。

?engine:服務(wù)引擎,這個可以理解為一個真正的服務(wù)器,內(nèi)部提供了多個虛擬主機(jī)對外服務(wù)。

?host:虛擬主機(jī),每一個虛擬主機(jī)相當(dāng)于一臺服務(wù)器,并且內(nèi)部可以部署多個應(yīng)用,每個虛擬主機(jī)可以綁定一個域名,并指定多個別名。

?context:應(yīng)用上下文,每一個webapp都有一個單獨(dú)的context,也可以理解為每一個context代表一個webapp。

?connector:連接器組件,可以配置多個連接器支持多種協(xié)議,如http,APJ 等

組件說明:

Tomcat常見組件:

?服務(wù)器(server):Tomcat的一個實(shí)例,通常一個JVM只能包含一個Tomcat實(shí)例;因此,一臺物理服務(wù)器上可以在啟動多個JVM的情況下在每一個JVM中啟動一個Tomcat實(shí)例,每個實(shí)例分屬于一個獨(dú)立的管理端口。這是一個頂級組件。

?服務(wù)(service):一個服務(wù)組件通常包含一個引擎和與此引擎相關(guān)聯(lián)的一個或多個連接器。給服務(wù)命名可以方便管理員在日志文件中識別不同服務(wù)產(chǎn)生的日志。一個server可以包含多個service組件,但通常情下只為一個service指派一個server。

連接器類組件:

?連接器(connectors):負(fù)責(zé)連接客戶端(可以是瀏覽器或Web服務(wù)器)請求至Servlet容器內(nèi)的Web應(yīng)用程序,通常指的是接收客戶發(fā)來請求的位置及服務(wù)器端分配的端口。默認(rèn)端口通常是HTTP協(xié)議的8080,管理員也可以根據(jù)自己的需要改變此端口。還可以支持HTTPS ,默認(rèn)HTTPS端口為8443。同時也支持AJP,即(A)一個引擎可以配置多個連接器,但這些連接器必須使用不同的端口。默認(rèn)的連接器是基于HTTP/1.1的Coyote。同時,Tomcat也支持AJP、JServ和JK2連接器。

容器類組件:

?引擎(Engine):引擎通是指處理請求的Servlet引擎組件,即Catalina Servlet引擎,它檢查每一個請求的HTTP首部信息以辨別此請求應(yīng)該發(fā)往哪個host或context,并將請求處理后的結(jié)果返回的相應(yīng)的客戶端。嚴(yán)格意義上來說,容器不必非得通過引擎來實(shí)現(xiàn),它也可以是只是一個容器。如果Tomcat被配置成為獨(dú)立服務(wù)器,默認(rèn)引擎就是已經(jīng)定義好的引擎。而如果Tomcat被配置為Apache Web服務(wù)器的提供Servlet功能的后端,默認(rèn)引擎將被忽略,因?yàn)閃eb服務(wù)器自身就能確定將用戶請求發(fā)往何處。一個引擎可以包含多個host組件。

?主機(jī)(Host):主機(jī)組件類似于Apache中的虛擬主機(jī),但在Tomcat中只支持基于FQDN的“虛擬主機(jī)”。一個引擎至少要包含一個主機(jī)組件。

?上下文(Context):Context組件是最內(nèi)層次的組件,它表示W(wǎng)eb應(yīng)用程序本身。配置一個Context最主要的是指定Web應(yīng)用程序的根目錄,以便Servlet容器能夠?qū)⒂脩粽埱蟀l(fā)往正確的位置。Context組件也可包含自定義的錯誤頁,以實(shí)現(xiàn)在用戶訪問發(fā)生錯誤時提供友好的提示信息。

被嵌套類(nested)組件:

這類組件通常包含于容器類組件中以提供具有管理功能的服務(wù),它們不能包含其它組件,但有些卻可以由不同層次的容器各自配置。

?閥門(Valve):用來攔截請求并在將其轉(zhuǎn)至目標(biāo)之前進(jìn)行某種處理操作,類似于Servlet規(guī)范中定義的過濾器。Valve可以定義在任何容器類的組件中。Valve常被用來記錄客戶端請求、客戶端IP地址和服務(wù)器等信息,這種處理技術(shù)通常被稱作請求轉(zhuǎn)儲(request dumping)。請求轉(zhuǎn)儲valve記錄請求客戶端請求數(shù)據(jù)包中的HTTP首部信息和cookie信息文件中,響應(yīng)轉(zhuǎn)儲valve則記錄響應(yīng)數(shù)據(jù)包首部信息和cookie信息至文件中。

?日志記錄器(Logger):用于記錄組件內(nèi)部的狀態(tài)信息,可被用于除Context之外的任何容器中。日志記錄的功能可被繼承,因此,一個引擎級別的Logger將會記錄引擎內(nèi)部所有組件相關(guān)的信息,除非某內(nèi)部組件定義了自己的Logger組件。

?領(lǐng)域(Realm):用于用戶的認(rèn)證和授權(quán);在配置一個應(yīng)用程序時,管理員可以為每個資源或資源組定義角色及權(quán)限,而這些訪問控制功能的生效需要通過Realm來實(shí)現(xiàn)。Realm的認(rèn)證可以基于文本文件、數(shù)據(jù)庫表、LDAP服務(wù)等來實(shí)現(xiàn)。Realm的效用會遍及整個引擎或頂級容器,因此,一個容器內(nèi)的所有應(yīng)用程序?qū)⒐蚕碛脩糍Y源。同時,Realm可以被其所在組件的子組件繼承,也可以被子組件中定義的Realm所覆蓋。

二、優(yōu)化思路

1.?網(wǎng)絡(luò)優(yōu)化

BIO、NIO、NIO2、APR,也就是阻塞與非阻塞

壓縮gzip、超時配置,防止close_wait過多。

1.1、非阻塞,Tomcat8已經(jīng)取消BIO

四種請求連接模型

HTTP/1.1

org.apache.coyote.http11.Http11Protocol 阻塞模式的連接協(xié)議

org.apache.coyote.http11.Http11NioProtocol 非阻塞模式的連接協(xié)議

org.apache.coyote.http11.Http11Nio2Protocol 非阻塞模式的連接協(xié)議

org.apache.coyote.http11.Http11AprProtocol – 本地連接協(xié)議

1.2、啟用壓縮,消耗CPU,減小網(wǎng)絡(luò)傳輸大小

compression="on"

disableUploadTimeout="true"

compressionMinSize="2048"

compressableMimeType="text/html,text/xml,text/plain,text/css,text/javascript,a

pplication/javascript"

URIEncoding="utf-8"

2. 并發(fā)優(yōu)化

最大線程數(shù)

最佳并發(fā)數(shù)。。。

連接數(shù):maxConnections(最大連接數(shù))

處理線程:maxThreads(操作系統(tǒng)允許多少線程,線程多大會引起切換效能)

等候隊(duì)列:acceptCount(排隊(duì)數(shù)量)指最大連接數(shù)已經(jīng)滿了的時候允許多少請求排隊(duì)

3. 底層優(yōu)化

JVM優(yōu)化

多實(shí)例(必須的)

操作系統(tǒng)優(yōu)化

JVM優(yōu)化:固定堆內(nèi)存,多線程并發(fā)收集,對象預(yù)留新生代,大對象進(jìn)入老年代,啟用內(nèi)聯(lián)

多實(shí)例:多個tomcat實(shí)例在一臺機(jī)上

操作系統(tǒng)優(yōu)化:網(wǎng)絡(luò)參數(shù),線程數(shù),關(guān)閉IPV6,最大文件數(shù)

Linux服務(wù)器每進(jìn)程不允許超過1000個線程,據(jù)說6、700線程服務(wù)器切換線程就慢下來

命令:ps -eLf | grep java | wc –l? 可以查看當(dāng)前啟動的java進(jìn)程里面有多少個線程

Linux線程棧大小是8M,可以使用ulimit –s設(shè)置

三、優(yōu)化實(shí)戰(zhàn)

1. 優(yōu)化tomcat.conf配置文件

/etc/tomcat/tomcat.conf文件修改JAVA_OPTS

JAVA_OPTS=“-server –Xmx2048m–Xms2048m –Xmn768m - XX:TargetSurvivorRatio=90 -XX:PetenureSizeThreshold=1000000 - XX:MaxTenuringThreshold=30 –XX:+UseParallelGC

–XX:+UseConcMarkSweepGC –XX:ParallelGCThreads=2"

2. 優(yōu)化server.conf配置文件

/etc/tomcat/server.conf文件修改配置

<Executor name="tomcatThreadPool" namePrefix="catalina-exec-" maxThreads="500" //最大并發(fā)數(shù),默認(rèn)設(shè)置 200,一般建議在 500 ~ 800,根據(jù)硬件設(shè)施和業(yè)務(wù)來判斷

minSpareThreads="100" //Tomcat 初始化時創(chuàng)建的線程數(shù),默認(rèn)設(shè)置 25

prestartminSpareThreads = "true"http://在 Tomcat 初始化的時候就初始化 minSpareThreads 的參數(shù)值,如果不等于 true,minSpareThreads 的值就無效

maxQueueSize = "100"http://最大的等待隊(duì)列數(shù),超過則拒絕請求 />

<Connector executor="tomcatThreadPool" port="8080"

protocol="org.apache.coyote.http11.Http11Nio2Protocol" //Tomcat 8 設(shè)置 nio2 更好,Tomcat 6 、7設(shè)置nio更好:org.apache.coyote.http11.Http11NioProtocol

connectionTimeout="20000"

minSpareThreads="100" maxSpareThreads="1000"最大處理連接數(shù)線程

minProcessors="100“同時處理請求的最小數(shù)

maxProcessors=“1000”同時處理請求的最大數(shù)

maxConnections="1000" redirectPort="8443"

enableLookups="false" //禁用DNS查詢 acceptCount="100" //指定當(dāng)所有可以使用的處理請求的線程數(shù)都被使用時,可以放到處理隊(duì)列中的請求數(shù),超過這個數(shù)的請求將不予處理,默認(rèn)設(shè)置 100

maxPostSize="10485760" //以 FORM URL 參數(shù)方式的 POST 提交方式,限制提交最大的大小,默認(rèn)是2097152(2兆),它使用的單位是字節(jié)。10485760 為 10M。如果要禁用限制,則可以設(shè)置為 -1。

compression="on" disableUploadTimeout="true" compressionMinSize="2048"

acceptorThreadCount="2" //用于接收連接的線程的數(shù)量,默認(rèn)值是1。一般這個指需要改動的時候是因?yàn)樵摲?wù)器是一個多核CPU,如果是多核CPU一般配置為 2.

compressableMimeType="text/html,text/xml,text/plain,text/css,text/javascript,application/ja

vascript" URIEncoding="utf-8" keepAliveTimeout="0"

關(guān)閉shutdown端口:<Server port="-1" shutdown="SHUTDOWN">

關(guān)閉ajp連接:注釋<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />

取消訪問日志Valve閥門

<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"

prefix="localhost_access_log." suffix=".txt"

pattern="%h %l %u %t &quot;%r&quot; %s %b" />

/>

圖示:

3. linux內(nèi)核優(yōu)化

3.1 linux 默認(rèn)值 open files 和 max user processes 為 1024

#ulimit -n

1024

#ulimit –u

1024

問題描述:

說明server只允許同時打開1024個文件,處理1024個用戶進(jìn)程,使用ulimit -a 可以查看當(dāng)前系統(tǒng)的所有限制值,使用ulimit -n可以查看當(dāng)前的最大打開文件數(shù)。新裝的linux 默認(rèn)只有1024,當(dāng)作負(fù)載較大的服務(wù)器時,很容易遇到error: too many open files 。

解決方法:

使用 ulimit –n 65535 可即時修改,但重啟后就無效了。

有如下三種修改方式:

在/etc/security/limits.conf 最后增加:

* soft nofile 65535

* hard nofile 65535

* soft nproc 65535

* hard nproc 65535

3.2 其他的linux配置優(yōu)化

net.ipv4.tcp_syncookies = 1 開啟SYN Cookies。當(dāng)出現(xiàn)SYN等待隊(duì)列溢出時,啟用cookies來處理,可防范少量SYN攻擊;

net.ipv4.tcp_tw_reuse = 1 開啟重用。允許將TIME-WAIT sockets重新用于新的TCP連接,默認(rèn)為0

net.ipv4.tcp_tw_recycle = 1 開啟TCP連接中TIME-WAIT sockets的快速回收,默認(rèn)為0,表示關(guān)閉。

net.ipv4.tcp_fin_timeout = 30 如果套接字由本端要求關(guān)閉,它決定了它保持在FIN-WAIT-2狀態(tài)的時間。

net.ipv4.tcp_keepalive_time = 1200 當(dāng)keepalive起用的時候,TCP發(fā)送keepalive消息的頻度。缺省是2小時

net.ipv4.tcp_keepalive_intvl = 30

net.ipv4.tcp_keepalive_probes = 3 probe 3次(每次30秒)不成功,內(nèi)核才徹底放棄。

tcp_keepalive_time = 7200 seconds (2 hours)

tcp_keepalive_probes = 9

tcp_keepalive_intvl = 75 seconds

net.ipv4.ip_local_port_range = 1024 65000 用于向外連接的端口范圍。缺省情況下很?。?2768到61000,改為1024到65000。

net.ipv4.tcp_max_syn_backlog = 8192 SYN隊(duì)列的長度,默認(rèn)為1024,加大隊(duì)列長度為8192,可以容納更多等待連接的網(wǎng)絡(luò)連接數(shù)。

net.ipv4.netdev_max_backlog = 1000 表示進(jìn)入包的最大設(shè)備隊(duì)列,默認(rèn)300,改大

net.core.tcp_max_tw_buckets = 5000 系統(tǒng)同時保持TIME_WAIT套接字的最大數(shù)量,如果超過這個數(shù)字,TIME_WAIT套接字將立刻被清除并打印警告信息。默認(rèn)為180000,改為 5000。

另外可以參考優(yōu)化內(nèi)核配置:

/proc/sys/net/core/wmem_max 最大socket寫buffer,可參考的優(yōu)化值:873200

/proc/sys/net/core/rmem_max 最大socket讀buffer,可參考的優(yōu)化值:873200

/proc/sys/net/ipv4/tcp_wmem TCP寫buffer,可參考的優(yōu)化值: 8192 436600 873200

/proc/sys/net/ipv4/tcp_rmem TCP讀buffer,可參考的優(yōu)化值: 32768 436600 873200

/proc/sys/net/ipv4/tcp_mem

同樣有3個值,意思是:配置單位為頁,不是字節(jié)

net.ipv4.tcp_mem[0]:低于此值,TCP沒有內(nèi)存壓力. 786432

net.ipv4.tcp_mem[1]:在此值下,進(jìn)入內(nèi)存壓力階段. 1048576

net.ipv4.tcp_mem[2]:高于此值,TCP拒絕分配socket. 1572864

/proc/sys/net/core/somaxconn 256

listen()的默認(rèn)參數(shù),掛起請求的最大數(shù)量.默認(rèn)是128.對繁忙的服務(wù)器,增加該值有助于網(wǎng)絡(luò)性能.

/proc/sys/net/core/optmem_max socket buffer的最大初始化值,默認(rèn)10K.

/proc/sys/net/ipv4/tcp_retries2 TCP失敗重傳次數(shù),默認(rèn)值15.減少到5,以盡早釋放內(nèi)核資源.

net.core.somaxconn = 32768 socket監(jiān)聽(listen)的backlog上限,是socket的監(jiān)聽隊(duì)列。比如nginx定義

NGX_LISTEN_BACKLOG默認(rèn)到511

4. nginx優(yōu)化

1. worker_processes 8;nginx 進(jìn)程數(shù),建議按照cpu 數(shù)目來指定,一般為它的倍數(shù) (如,2個四核的cpu計為8)。

2. worker_cpu_affinity 00000001 00000010 00000100 00001000 00010000 00100000 01000000 10000000;為每個進(jìn)程分配cpu,上例中將8個進(jìn)程分配到8個cpu,當(dāng)然可以寫多個,或者將一個進(jìn)程分配到多個cpu。

3. worker_rlimit_nofile 65535;這個指令是指當(dāng)一個nginx 進(jìn)程打開的最多文件描述符數(shù)目,理論值應(yīng)該是最多打開文件數(shù)(ulimit -n)與nginx進(jìn)程數(shù)相除,但是nginx分配請求并不是那么均勻,所以最好與ulimit -n 的值保持一致。

查看linux系統(tǒng)文件描述符的方法:

[root@web001 ~]# sysctl -a | grep fs.file

fs.file-max = 789972

fs.file-nr = 510 0 789972

4. use epoll; 使用epoll 的I/O 模型

5. worker_connections 65535;每個進(jìn)程允許的最多連接數(shù),理論上每臺nginx 服務(wù)器的最大連接數(shù)為worker_processes*worker_connections。

6. keepalive_timeout 60;keepalive 超時時間。

7. client_header_buffer_size 4k;客戶端請求頭部的緩沖區(qū)大小,這個可以根據(jù)你的系統(tǒng)分頁大小來設(shè)置,一般一個請求頭的大小不會超過1k,不過由于一般系統(tǒng)分頁都要大于1k,所以這里設(shè)置為分頁大小。分頁大小可以用命令getconf PAGESIZE 取得。

[root@web001 ~]# getconf PAGESIZE

4096

但也有client_header_buffer_size超過4k的情況,但是client_header_buffer_size該值必須設(shè)置為“系統(tǒng)分頁大小”的整倍數(shù)。

8. open_file_cache max=65535 inactive=60s;這個將為打開文件指定緩存,默認(rèn)是沒有啟用的,max 指定緩存數(shù)量,建議和打開文件數(shù)一致,inactive 是指經(jīng)過多長時間文件沒被請求后刪除緩存。

9. open_file_cache_valid 80s;這個是指多長時間檢查一次緩存的有效信息。

10. open_file_cache_min_uses 1;open_file_cache 指令中的inactive 參數(shù)時間內(nèi)文件的最少使用次數(shù),如果超過這個數(shù)字,文件描述符一直是在緩存中打開的,如上例,如果有一個文件在inactive 時間內(nèi)一次沒被使用,它將被移除。

四、集群優(yōu)化

當(dāng)線程數(shù)達(dá)到250以上,考慮群集部署,集群部署需要考慮的兩個問題:Tomcat部署和session共享,Tomcat<4時,可用tomcat內(nèi)部的集群session共享,否則采用redis方式集群

集群部署原理圖:

redis實(shí)現(xiàn)session共享的原理


Redis實(shí)現(xiàn)seesion共享的步驟如下:

1.?下載以下包放到tomcat的lib目錄下

TomcatRedisSessionManager-1.1 .jar

Jredis-2.8.0.jar

Commons-logging-1.2.jar

Commons-pool2-2.4.1.jar

2. 在tomcat里面增加如下配置

<Valve className="tomcat.request.session.redis.RequestSessionHandlerValve"/>

<Manager className="tomcat.request.session.redis.RequestSessionManager"/>


?創(chuàng)建一個redis的配置文件redis-data-cache.properties,放在conf.d目錄

redis.hosts=127.0.0.1:6379

redis.cluster.enabled=false

#- redis database (default 0)

#redis.database=0

#- redis connection timeout (default 2000)

#redis.timeout=2000

五、壓力測試

Ab測試

吞吐率(Requests per second):總請求數(shù) / 處理完成這些請求數(shù)所花費(fèi)的時間

并發(fā)連接數(shù)(The number of concurrent users,Concurrency Level):一個用戶可能同時會產(chǎn)生多個會話,也即連接數(shù)

用戶平均請求等待時間(Time per request):處理完成所有請求數(shù)所花費(fèi)的時間/ (總請求數(shù) / 并發(fā)用戶數(shù))

服務(wù)器平均請求等待時間(Time per request: across all concurrent?requests)計 算 公 式 : 處 理 完 成 所 有 請 求 數(shù) 所 花 費(fèi) 的 時 間 / 總 請 求 數(shù)

使用示例:

ab –n 1000 –c 100 url/

如果只用到一個Cookie,那么只需鍵入命令:

ab -n 100 -C key=value http://test.com/

如果需要多個Cookie,就直接設(shè)Header:

ab -n 100 -H “Cookie: Key1=Value1; Key2=Value2” http://test.com/

歡迎工作一到五年的Java工程師朋友們加入Java架構(gòu)開發(fā):760940986

群內(nèi)提供免費(fèi)的Java架構(gòu)學(xué)習(xí)資料(里面有高可用、高并發(fā)、高性能及分布式、Jvm性能調(diào)優(yōu)、Spring源碼,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個知識點(diǎn)的架構(gòu)資料)合理利用自己每一分每一秒的時間來學(xué)習(xí)提升自己,不要再用"沒有時間“來掩飾自己思想上的懶惰!趁年輕,使勁拼,給未來的自己一個交代!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容