一、Tomcat工作原理
1. TCP的三次握手四次揮手
三次握手:
說明:
類比於A和B打電話:
A對B說:你好,我是A,你能聽到我說話嗎?
B對A說:嗯,我能聽到你說話
A對B說:好,那我們開始聊天吧
在服務器上使用如下命令能看到當前服務器的連接情況
netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
返回結果說明:
LAST_ACK 5 (正在等待處理的請求數)
SYN_RECV 30
ESTABLISHED 1597 (正常數據傳輸狀態)
FIN_WAIT1 51
FIN_WAIT2 504
TIME_WAIT 1057 (處理完畢,等待超時結束的請求數)
四次揮手:
說明:
同樣用A和B打電話來說明:
A對B說:我說完了,我要掛電話了
B對A說:等一下,我還沒說完
B繼續對A說:我說完了,你可以掛電話了
A對B說:好,我掛電話了
其他參數說明:
CLOSED:無連接是活動的或正在進行
LISTEN:服務器在等待進入呼叫
SYN_RECV:一個連接請求已經到達,等待確認
SYN_SENT:應用已經開始,打開一個連接
ESTABLISHED:正常數據傳輸狀態
FIN_WAIT1:應用說它已經完成
FIN_WAIT2:另一邊已同意釋放
ITMED_WAIT:等待所有分組死掉
CLOSING:兩邊同時嘗試關閉
TIME_WAIT:另一邊已初始化一個釋放
LAST_ACK:等待所有分組死掉
2. Tomcat內部結構
上圖說明:
•server:指的是整個應用的上下文, 也是最頂層的容器,tomcat中所有的東西都在這個server里邊。
•service:指的是一個服務,主要的功能是把connector組件和engine組織起來,使得通過connector組件與整個容器通訊的應用可以使用engine提供的服務。
•engine:服務引擎,這個可以理解為一個真正的服務器,內部提供了多個虛擬主機對外服務。
•host:虛擬主機,每一個虛擬主機相當於一台服務器,並且內部可以部署多個應用,每個虛擬主機可以綁定一個域名,並指定多個別名。
•context:應用上下文,每一個webapp都有一個單獨的context,也可以理解為每一個context代表一個webapp。
•connector:連接器組件,可以配置多個連接器支持多種協議,如http,APJ 等
組件說明:
Tomcat常見組件:
•服務器(server):Tomcat的一個實例,通常一個JVM只能包含一個Tomcat實例;因此,一台物理服務器上可以在啟動多個JVM的情況下在每一個JVM中啟動一個Tomcat實例,每個實例分屬於一個獨立的管理端口。這是一個頂級組件。
•服務(service):一個服務組件通常包含一個引擎和與此引擎相關聯的一個或多個連接器。給服務命名可以方便管理員在日志文件中識別不同服務產生的日志。一個server可以包含多個service組件,但通常情下只為一個service指派一個server。
連接器類組件:
•連接器(connectors):負責連接客戶端(可以是瀏覽器或Web服務器)請求至Servlet容器內的Web應用程序,通常指的是接收客戶發來請求的位置及服務器端分配的端口。默認端口通常是HTTP協議的8080,管理員也可以根據自己的需要改變此端口。還可以支持HTTPS ,默認HTTPS端口為8443。同時也支持AJP,即(A)一個引擎可以配置多個連接器,但這些連接器必須使用不同的端口。默認的連接器是基於HTTP/1.1的Coyote。同時,Tomcat也支持AJP、JServ和JK2連接器。
容器類組件:
•引擎(Engine):引擎通是指處理請求的Servlet引擎組件,即Catalina Servlet引擎,它檢查每一個請求的HTTP首部信息以辨別此請求應該發往哪個host或context,並將請求處理后的結果返回的相應的客戶端。嚴格意義上來說,容器不必非得通過引擎來實現,它也可以是只是一個容器。如果Tomcat被配置成為獨立服務器,默認引擎就是已經定義好的引擎。而如果Tomcat被配置為Apache Web服務器的提供Servlet功能的后端,默認引擎將被忽略,因為Web服務器自身就能確定將用戶請求發往何處。一個引擎可以包含多個host組件。
•主機(Host):主機組件類似於Apache中的虛擬主機,但在Tomcat中只支持基於FQDN的“虛擬主機”。一個引擎至少要包含一個主機組件。
•上下文(Context):Context組件是最內層次的組件,它表示Web應用程序本身。配置一個Context最主要的是指定Web應用程序的根目錄,以便Servlet容器能夠將用戶請求發往正確的位置。Context組件也可包含自定義的錯誤頁,以實現在用戶訪問發生錯誤時提供友好的提示信息。
被嵌套類(nested)組件:
這類組件通常包含於容器類組件中以提供具有管理功能的服務,它們不能包含其它組件,但有些卻可以由不同層次的容器各自配置。
•閥門(Valve):用來攔截請求並在將其轉至目標之前進行某種處理操作,類似於Servlet規范中定義的過濾器。Valve可以定義在任何容器類的組件中。Valve常被用來記錄客戶端請求、客戶端IP地址和服務器等信息,這種處理技術通常被稱作請求轉儲(request dumping)。請求轉儲valve記錄請求客戶端請求數據包中的HTTP首部信息和cookie信息文件中,響應轉儲valve則記錄響應數據包首部信息和cookie信息至文件中。
•日志記錄器(Logger):用於記錄組件內部的狀態信息,可被用於除Context之外的任何容器中。日志記錄的功能可被繼承,因此,一個引擎級別的Logger將會記錄引擎內部所有組件相關的信息,除非某內部組件定義了自己的Logger組件。
•領域(Realm):用於用戶的認證和授權;在配置一個應用程序時,管理員可以為每個資源或資源組定義角色及權限,而這些訪問控制功能的生效需要通過Realm來實現。Realm的認證可以基於文本文件、數據庫表、LDAP服務等來實現。Realm的效用會遍及整個引擎或頂級容器,因此,一個容器內的所有應用程序將共享用戶資源。同時,Realm可以被其所在組件的子組件繼承,也可以被子組件中定義的Realm所覆蓋。
二、優化思路
1. 網絡優化
BIO、NIO、NIO2、APR,也就是阻塞與非阻塞
壓縮gzip、超時配置,防止close_wait過多。
1.1、非阻塞,Tomcat8已經取消BIO
四種請求連接模型
HTTP/1.1
org.apache.coyote.http11.Http11Protocol 阻塞模式的連接協議
org.apache.coyote.http11.Http11NioProtocol 非阻塞模式的連接協議
org.apache.coyote.http11.Http11Nio2Protocol 非阻塞模式的連接協議
org.apache.coyote.http11.Http11AprProtocol – 本地連接協議
1.2、啟用壓縮,消耗CPU,減小網絡傳輸大小
compression="on"
disableUploadTimeout="true"
compressionMinSize="2048"
compressableMimeType="text/html,text/xml,text/plain,text/css,text/javascript,a
pplication/javascript"
URIEncoding="utf-8"
2. 並發優化
最大線程數
最佳並發數。。。
連接數:maxConnections(最大連接數)
處理線程:maxThreads(操作系統允許多少線程,線程多大會引起切換效能)
等候隊列:acceptCount(排隊數量)指最大連接數已經滿了的時候允許多少請求排隊
3. 底層優化
JVM優化
多實例(必須的)
操作系統優化
JVM優化:固定堆內存,多線程並發收集,對象預留新生代,大對象進入老年代,啟用內聯
多實例:多個tomcat實例在一台機上
操作系統優化:網絡參數,線程數,關閉IPV6,最大文件數
Linux服務器每進程不允許超過1000個線程,據說6、700線程服務器切換線程就慢下來
命令:ps -eLf | grep java | wc –l 可以查看當前啟動的java進程里面有多少個線程
Linux線程棧大小是8M,可以使用ulimit –s設置
三、優化實戰
1. 優化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. 優化server.conf配置文件
/etc/tomcat/server.conf文件修改配置
<Executor name="tomcatThreadPool" namePrefix="catalina-exec-" maxThreads="500" //最大並發數,默認設置 200,一般建議在 500 ~ 800,根據硬件設施和業務來判斷 minSpareThreads="100" //Tomcat 初始化時創建的線程數,默認設置 25 prestartminSpareThreads = "true"//在 Tomcat 初始化的時候就初始化 minSpareThreads 的參數值,如果不等於 true,minSpareThreads 的值就無效 maxQueueSize = "100"//最大的等待隊列數,超過則拒絕請求 /> <Connector executor="tomcatThreadPool" port="8080" protocol="org.apache.coyote.http11.Http11Nio2Protocol" //Tomcat 8 設置 nio2 更好,Tomcat 6 、7設置nio更好:org.apache.coyote.http11.Http11NioProtocol connectionTimeout="20000" minSpareThreads="100" maxSpareThreads="1000"最大處理連接數線程 minProcessors="100“同時處理請求的最小數 maxProcessors=“1000”同時處理請求的最大數 maxConnections="1000" redirectPort="8443" enableLookups="false" //禁用DNS查詢 acceptCount="100" //指定當所有可以使用的處理請求的線程數都被使用時,可以放到處理隊列中的請求數,超過這個數的請求將不予處理,默認設置 100 maxPostSize="10485760" //以 FORM URL 參數方式的 POST 提交方式,限制提交最大的大小,默認是2097152(2兆),它使用的單位是字節。10485760 為 10M。如果要禁用限制,則可以設置為 -1。 compression="on" disableUploadTimeout="true" compressionMinSize="2048" acceptorThreadCount="2" //用於接收連接的線程的數量,默認值是1。一般這個指需要改動的時候是因為該服務器是一個多核CPU,如果是多核CPU一般配置為 2. compressableMimeType="text/html,text/xml,text/plain,text/css,text/javascript,application/ja vascript" URIEncoding="utf-8" keepAliveTimeout="0" 關閉shutdown端口:<Server port="-1" shutdown="SHUTDOWN"> 關閉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 "%r" %s %b" /> />
圖示:
3. linux內核優化
3.1 linux 默認值 open files 和 max user processes 為 1024
#ulimit -n
1024
#ulimit –u
1024
問題描述:
說明server只允許同時打開1024個文件,處理1024個用戶進程,使用ulimit -a 可以查看當前系統的所有限制值,使用ulimit -n可以查看當前的最大打開文件數。新裝的linux 默認只有1024,當作負載較大的服務器時,很容易遇到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配置優化
net.ipv4.tcp_syncookies = 1 開啟SYN Cookies。當出現SYN等待隊列溢出時,啟用cookies來處理,可防范少量SYN攻擊; net.ipv4.tcp_tw_reuse = 1 開啟重用。允許將TIME-WAIT sockets重新用於新的TCP連接,默認為0 net.ipv4.tcp_tw_recycle = 1 開啟TCP連接中TIME-WAIT sockets的快速回收,默認為0,表示關閉。 net.ipv4.tcp_fin_timeout = 30 如果套接字由本端要求關閉,它決定了它保持在FIN-WAIT-2狀態的時間。 net.ipv4.tcp_keepalive_time = 1200 當keepalive起用的時候,TCP發送keepalive消息的頻度。缺省是2小時 net.ipv4.tcp_keepalive_intvl = 30 net.ipv4.tcp_keepalive_probes = 3 probe 3次(每次30秒)不成功,內核才徹底放棄。 tcp_keepalive_time = 7200 seconds (2 hours) tcp_keepalive_probes = 9 tcp_keepalive_intvl = 75 seconds net.ipv4.ip_local_port_range = 1024 65000 用於向外連接的端口范圍。缺省情況下很小:32768到61000,改為1024到65000。 net.ipv4.tcp_max_syn_backlog = 8192 SYN隊列的長度,默認為1024,加大隊列長度為8192,可以容納更多等待連接的網絡連接數。 net.ipv4.netdev_max_backlog = 1000 表示進入包的最大設備隊列,默認300,改大 net.core.tcp_max_tw_buckets = 5000 系統同時保持TIME_WAIT套接字的最大數量,如果超過這個數字,TIME_WAIT套接字將立刻被清除並打印警告信息。默認為180000,改為 5000。 另外可以參考優化內核配置: /proc/sys/net/core/wmem_max 最大socket寫buffer,可參考的優化值:873200 /proc/sys/net/core/rmem_max 最大socket讀buffer,可參考的優化值:873200 /proc/sys/net/ipv4/tcp_wmem TCP寫buffer,可參考的優化值: 8192 436600 873200 /proc/sys/net/ipv4/tcp_rmem TCP讀buffer,可參考的優化值: 32768 436600 873200 /proc/sys/net/ipv4/tcp_mem 同樣有3個值,意思是:配置單位為頁,不是字節 net.ipv4.tcp_mem[0]:低於此值,TCP沒有內存壓力. 786432 net.ipv4.tcp_mem[1]:在此值下,進入內存壓力階段. 1048576 net.ipv4.tcp_mem[2]:高於此值,TCP拒絕分配socket. 1572864 /proc/sys/net/core/somaxconn 256 listen()的默認參數,掛起請求的最大數量.默認是128.對繁忙的服務器,增加該值有助於網絡性能. /proc/sys/net/core/optmem_max socket buffer的最大初始化值,默認10K. /proc/sys/net/ipv4/tcp_retries2 TCP失敗重傳次數,默認值15.減少到5,以盡早釋放內核資源. net.core.somaxconn = 32768 socket監聽(listen)的backlog上限,是socket的監聽隊列。比如nginx定義 NGX_LISTEN_BACKLOG默認到511
4. nginx優化
1. worker_processes 8;nginx 進程數,建議按照cpu 數目來指定,一般為它的倍數 (如,2個四核的cpu計為8)。
2. worker_cpu_affinity 00000001 00000010 00000100 00001000 00010000 00100000 01000000 10000000;為每個進程分配cpu,上例中將8個進程分配到8個cpu,當然可以寫多個,或者將一個進程分配到多個cpu。
3. worker_rlimit_nofile 65535;這個指令是指當一個nginx 進程打開的最多文件描述符數目,理論值應該是最多打開文件數(ulimit -n)與nginx進程數相除,但是nginx分配請求並不是那么均勻,所以最好與ulimit -n 的值保持一致。
查看linux系統文件描述符的方法:
[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;每個進程允許的最多連接數,理論上每台nginx 服務器的最大連接數為worker_processes*worker_connections。
6. keepalive_timeout 60;keepalive 超時時間。
7. client_header_buffer_size 4k;客戶端請求頭部的緩沖區大小,這個可以根據你的系統分頁大小來設置,一般一個請求頭的大小不會超過1k,不過由於一般系統分頁都要大於1k,所以這里設置為分頁大小。分頁大小可以用命令getconf PAGESIZE 取得。
[root@web001 ~]# getconf PAGESIZE
4096
但也有client_header_buffer_size超過4k的情況,但是client_header_buffer_size該值必須設置為“系統分頁大小”的整倍數。
8. open_file_cache max=65535 inactive=60s;這個將為打開文件指定緩存,默認是沒有啟用的,max 指定緩存數量,建議和打開文件數一致,inactive 是指經過多長時間文件沒被請求后刪除緩存。
9. open_file_cache_valid 80s;這個是指多長時間檢查一次緩存的有效信息。
10. open_file_cache_min_uses 1;open_file_cache 指令中的inactive 參數時間內文件的最少使用次數,如果超過這個數字,文件描述符一直是在緩存中打開的,如上例,如果有一個文件在inactive 時間內一次沒被使用,它將被移除。
四、集群優化
當線程數達到250以上,考慮群集部署,集群部署需要考慮的兩個問題:Tomcat部署和session共享,Tomcat<4時,可用tomcat內部的集群session共享,否則采用redis方式集群
集群部署原理圖:
redis實現session共享的原理
Redis實現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"/>
創建一個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):總請求數 / 處理完成這些請求數所花費的時間
並發連接數(The number of concurrent users,Concurrency Level):一個用戶可能同時會產生多個會話,也即連接數
用戶平均請求等待時間(Time per request):處理完成所有請求數所花費的時間/ (總請求數 / 並發用戶數)
服務器平均請求等待時間(Time per request: across all concurrent requests)計 算 公 式 : 處 理 完 成 所 有 請 求 數 所 花 費 的 時 間 / 總 請 求 數
使用示例:
ab –n 1000 –c 100 url/
如果只用到一個Cookie,那么只需鍵入命令:
ab -n 100 -C key=value http://test.com/
如果需要多個Cookie,就直接設Header:
ab -n 100 -H “Cookie: Key1=Value1; Key2=Value2” http://test.com/