背景
最近在做壓測 , 同事覺得 Tomcat 不行想要切換 web 服務(wù)器 , 于是我就去網(wǎng)上搜了一些測評結(jié)果 , 不出意外 Tomcat 得到了一致的差評螟碎。抱著懷疑的態(tài)度 , 我決定對 SpringBoot 內(nèi)嵌支持的三款 Servlet 容器 ( Tomcat 楣嘁、Jetty 和 Undertow ) 做一個簡單的對比測試 , 測試結(jié)果僅供參考 。
測試環(huán)境
項目 | 描述 |
---|---|
測試端主機 | 阿里云ECS(ecs.sn1ne.2xlarge) 8C16G CentOS7.4.1708 Linux Kernel 3.10.0
|
被測端主機 | 阿里云ECS(ecs.sn1ne.2xlarge) 8C16G CentOS7.4.1708 Linux Kernel 3.10.0
|
網(wǎng)絡(luò)環(huán)境 | 阿里云專有網(wǎng)絡(luò) 內(nèi)網(wǎng)帶寬: 2 Gbps |
測試工具 | wrk |
項目框架 | SpringBoot 1.5.10.RELEASE |
JDK |
1.8.0_191 Java HotSpot(TM) 64-Bit Server VM
|
測試容器 |
Tomcat Jetty Undertow
|
說明:
測試端和被測端分別在兩臺主機上 , 為了避免放在一臺主機上導(dǎo)致互相搶占 CPU 而無法得到真實結(jié)果帆锋。
主角是 Servlet容器 , 所以網(wǎng)絡(luò)環(huán)境不應(yīng)該是這次測試的瓶頸 ( 系統(tǒng)內(nèi)核的 TCP 參數(shù)也要優(yōu)化 ) 吵取。
測試工具用過 wrk 、ab 锯厢、jmeter , 測試的目的是為了獲取被測端的性能 , 但不能因為測試工具的性能瓶頸而埋沒了被測端的真實能力 , 因此選用簡單高效的 wrk ( 下載源碼編譯安裝 ) 皮官。
測試方法
使用最簡單的 HTTP 接口 , 不包含任何業(yè)務(wù)邏輯和數(shù)據(jù)庫操作 , 反映容器的極致性能
@RestController
@RequestMapping("/api")
public class TestController {
@RequestMapping("/test")
public String test() {
return "This is a test result.";
}
}
使用 wrk 5000 并發(fā)下持續(xù)壓測 10 分鐘 ( 超時時間設(shè)為 30s , 避免超時時間過短帶來的大量錯誤 )
./wrk -t 8 -c 5000 -d 10m -T 30s "http://yourhostip:3000/api/test"
說明:
JVM 內(nèi)存設(shè)置為 2G 大小
項目啟動后需要預(yù)熱 ( JIT 編譯、加線程等等 ) 一下才能達到最優(yōu)的性能实辑。
為了避免性能數(shù)據(jù)波動造成的影響 , 每種情況測試 3 遍后取最優(yōu)結(jié)果捺氢。
按理說測試時間越長越能反映真實情況,不過試了 30 分鐘的測試 , 一趟測試下來耗費了一天時間 , 結(jié)果和 10 分鐘的也差不了多少 , 所以還是節(jié)省點時間吧徙菠。
測試結(jié)果
Tomcat 8.0.53 NIO 模式
-
啟動命令:
java -Xms2g -Xmx2g -Xmn1g -XX:MaxMetaspaceSize=256m -Xss256k -jar servlet-test-tomcat-8-nio.jar
-
監(jiān)控項:
wrk 的 CPU 使用率: 161.3% ( 最大 800% ) Server 的 CPU 使用率: 52.4 us, 13.0 sy, 0.0 ni, 16.8 id, 0.0 wa, 0.0 hi, 17.8 si, 0.0 st Server GC 情況:YGC: 870 YGCT: 5.3 FGC:0
-
wrk 結(jié)果:
Thread Stats Avg Stdev Max +/- Stdev Latency 86.31ms 7.23ms 778.24ms 88.50% Req/Sec 7.25k 420.33 14.55k 72.93% 34639518 requests in 10.00m, 5.33GB read Socket errors: connect 0, read 1253, write 2928, timeout 0 Requests/sec: 57726.28 Transfer/sec: 9.09MB
Tomcat 8.5.34 NIO 模式
-
啟動命令:
java -Xms2g -Xmx2g -Xmn1g -XX:MaxMetaspaceSize=256m -Xss256k -jar servlet-test-tomcat-8.5-nio.jar
-
監(jiān)控項:
wrk 的 CPU 使用率: 132.3% ( 最大 800% ) Server 的 CPU 使用率: 60.2 us, 14.1 sy, 0.0 ni, 19.1 id, 0.0 wa, 0.0 hi, 6.5 si, 0.0 st Server GC 情況:YGC: 940 YGCT: 5.736 FGC:0
-
wrk 結(jié)果:
Thread Stats Avg Stdev Max +/- Stdev Latency 83.43ms 9.79ms 1.70s 96.22% Req/Sec 7.51k 408.60 14.48k 73.85% 35852565 requests in 10.00m, 4.55GB read Socket errors: connect 0, read 1165, write 2883, timeout 0 Requests/sec: 59745.50 Transfer/sec: 7.76MB
Tomcat 8.0.53 APR 模式 ( 服務(wù)器上需要安裝 apr 類庫 )
-
啟動命令:
java -Xms2g -Xmx2g -Xmn1g -XX:MaxMetaspaceSize=256m -Xss256k -Djava.library.path=/usr/local/apr/lib -jar servlet-test-tomcat-8-apr.jar
-
監(jiān)控項:
wrk 的 CPU 使用率: 183.4% ( 最大 800% ) Server 的 CPU 使用率: 69.5 us, 13.7 sy, 0.0 ni, 3.7 id, 0.0 wa, 0.0 hi, 13.1 si, 0.0 st Server GC 情況:YGC: 1224 YGCT: 6.251 FGC:0
-
wrk 結(jié)果:
Thread Stats Avg Stdev Max +/- Stdev Latency 61.71ms 71.59ms 7.29s 99.87% Req/Sec 10.36k 441.68 18.63k 78.23% 49480257 requests in 10.00m, 7.61GB read Socket errors: connect 0, read 1388, write 814, timeout 0 Requests/sec: 82453.62 Transfer/sec: 12.99MB
Jetty 9.4.8.v20171121
-
啟動命令:
java -Xms2g -Xmx2g -Xmn1g -XX:MaxMetaspaceSize=256m -Xss256k -jar servlet-test-jetty.jar
-
監(jiān)控項:
wrk 的 CPU 使用率: 11.3% ( 最大 800% ) Server 的 CPU 使用率: 98.6 us, 0.5 sy, 0.0 ni, 0.5 id, 0.0 wa, 0.0 hi, 0.4 si, 0.0 st Server GC 情況:YGC: 640 YGCT: 6.541 FGC:0
-
wrk 結(jié)果:
Thread Stats Avg Stdev Max +/- Stdev Latency 126.01ms 435.50ms 29.95s 90.64% Req/Sec 206.88 127.12 1.03k 70.75% 978373 requests in 10.00m, 128.76MB read Socket errors: connect 0, read 95571, write 7191, timeout 331 Requests/sec: 1630.39 Transfer/sec: 219.72KB
Undertow 1.4.22.Final
-
啟動命令:
java -Xms2g -Xmx2g -Xmn1g -XX:MaxMetaspaceSize=256m -Xss256k -jar servlet-test-undertow.jar
-
監(jiān)控項:
wrk 的 CPU 使用率: 32.6% ( 最大 800% ) Server 的 CPU 使用率: 93.6 us, 2.4 sy, 0.0 ni, 1.5 id, 0.0 wa, 0.0 hi, 2.5 si, 0.0 st Server GC 情況:YGC: 370 YGCT: 1.684 FGC:0
-
wrk 結(jié)果:
Thread Stats Avg Stdev Max +/- Stdev Latency 354.95ms 138.95ms 1.93s 75.08% Req/Sec 1.77k 199.04 4.19k 72.85% 8471058 requests in 10.00m, 1.28GB read Socket errors: connect 0, read 1601, write 2882, timeout 0 Requests/sec: 14117.10 Transfer/sec: 2.18MB
Undertow 1.4.22.Final 配置線程數(shù)
-
啟動命令:
java -Xms2g -Xmx2g -Xmn1g -XX:MaxMetaspaceSize=256m -Xss256k -Dserver.undertow.io-threads=16 -Dserver.undertow.worker-threads=256 -jar servlet-test-undertow.jar
-
監(jiān)控項:
wrk 的 CPU 使用率: 33.3% ( 最大 800% ) Server 的 CPU 使用率: 92.1 us, 2.4 sy, 0.0 ni, 2.3 id, 0.0 wa, 0.0 hi, 3.2 si, 0.0 st Server GC 情況:YGC: 367 YGCT: 2.23 FGC:0
-
wrk 結(jié)果:
Thread Stats Avg Stdev Max +/- Stdev Latency 391.49ms 333.83ms 3.23s 60.47% Req/Sec 1.76k 235.55 4.51k 69.41% 8389131 requests in 10.00m, 1.27GB read Socket errors: connect 0, read 3987, write 3486, timeout 0 Requests/sec: 13980.36 Transfer/sec: 2.16MB
Undertow 2.0.15.Final
-
啟動命令:
java -Xms2g -Xmx2g -Xmn1g -XX:MaxMetaspaceSize=256m -Xss256k -jar servlet-test-undertow2.jar
-
監(jiān)控項:
wrk 的 CPU 使用率: 31.6% ( 最大 800% ) Server 的 CPU 使用率: 90.0 us, 2.6 sy, 0.0 ni, 4.1 id, 0.0 wa, 0.0 hi, 3.3 si, 0.0 st Server GC 情況:YGC: 361 YGCT: 2.676 FGC:0
-
wrk 結(jié)果:
Thread Stats Avg Stdev Max +/- Stdev Latency 368.09ms 17.14ms 942.66ms 97.28% Req/Sec 1.70k 164.17 4.01k 76.01% 8118614 requests in 10.00m, 1.22GB read Socket errors: connect 0, read 686, write 1453, timeout 0 Requests/sec: 13530.04 Transfer/sec: 2.09MB
測試結(jié)果分析
Servlet 容器 | QPS | QPS / GC 次數(shù) |
---|---|---|
Tomcat 8.0.53 NIO 模式 | 57726.28 | 57726.28 / 870 = 66.352 |
Tomcat 8.5.34 NIO 模式 | 59745.50 | 59745.50 / 940 = 63.559 |
Tomcat 8.0.53 APR 模式 | 82453.62 ( 最優(yōu) ) | 82453.62 / 1224 = 67.364 |
Jetty 9.4.8.v20171121 | 1630.39 ( 最差 ) | 1630.39 / 640 = 2.547 |
Undertow 1.4.22.Final | 14117.10 | 14117.10 / 370 = 38.154 |
Undertow 1.4.22.Final 配置線程數(shù) | 13980.36 | 13980.36 / 367 = 38.094 |
Undertow 2.0.15.Final | 13530.04 | 13530.04 / 361 = 37.479 |
先說結(jié)果: Tomcat APR 模式 "最優(yōu)" , Jetty "最差" , Undertow 也沒有想象中的碾壓 Tomcat
Tomcat NIO 模式 壓力剛上來的時候 CPU 會用滿 , 穩(wěn)定后基本會留有 10% 以上的空閑
Jetty 采用的默認配置 , 才 2k 不到的 QPS 確實有點出乎意料的 , 需要配置后做進一步的測試對比
Undertow 提供的配置項很少 , 只有 線程數(shù) 和 緩沖大小 , 加大 線程數(shù) 后也沒能提升性能讯沈。本次測試中 Undertow2.0 版本也沒有性能上的提升。
Tomcat8.5 相對于 Tomcat8.0 應(yīng)該有性能上的提升 , 由于系統(tǒng)中安裝的 apr 庫的版本問題沒有測試 Tomcat8.5+APR 的性能 ( Tomcat 版本和 apr 版本要對應(yīng) )
默認線程數(shù): Undertow 為 8 ( CPU 核心數(shù) ) 個 IO 線程 + 64 ( 8 * IO 線程數(shù) ) 個 WORK 線程, Tomcat 和 Jetty 均在 200 左右婿奔。因為都用的 NIO , 所以調(diào)大線程數(shù)效果不大 , 反而帶來 CPU上下文切換 和 內(nèi)存消耗 ( -Xss ) 的問題
聽人說 Undertow 在正常運行中會莫名其妙掛掉 , 具體原因還不清楚
優(yōu)化
-
JVM 參數(shù)
曾經(jīng)我也比較信奉所謂的 標準參數(shù) , 然而按照上述方法單獨對 Tomcat 進行對比測試后并沒有得到所謂的 "參數(shù)調(diào)優(yōu)" 的效果, 所以還是 針對實際運行情況再做調(diào)整吧缺狠。
主要幾類配置項有:-server
內(nèi)存 ( -Xms -Xmx -Xmn -Xss )
GC 相關(guān)參數(shù) ( 打印日志 / DisableExplicitGC /ExplicitGCInvokesConcurrent / 定時 GC )
-
Connector 運行模式
Tomcat Connector 有 BIO 、NIO 萍摊、APR 三種運行模式 ( 可以看下這篇 文章 ) , 雖然 APR 模式性能最好 ( 不絕對 ) , 但也帶來了維護上的成本 , 所以還是那句話 —— 針對實際的業(yè)務(wù)場景選擇
-
項目運行參數(shù)
SpringBoot 中幾個容器的可配置項都在 org.springframework.boot.autoconfigure.web.ServerProperties 這個類中, 一般默認就行了, 遇到問題再調(diào)整 ( Tomcat 相關(guān)配置可以看 這里 )
結(jié)語
-
本次測試結(jié)果和大部分網(wǎng)上的測試正好相反挤茄。
網(wǎng)上的測試:在一臺機器上跑 , 用例并發(fā)量比較小 , 得到的 QPS 也比較低 ( 幾百的數(shù)值而且比較接近 ) ,也沒有說明測試使用的機器配置和容器版本 , 只是簡單的把 Tomcat 歸類為性能最差冰木。這樣的測試結(jié)論我認為是很不可靠的穷劈。
時間有限 , 以上只是部分的測試用例 , 實際可以組合的情況 ( 服務(wù)器配置、容器版本踊沸、實際業(yè)務(wù)代碼等等 ) 多了去了 , 所以再強調(diào)一遍 —— 本次測試結(jié)果僅供參考 歇终。
-
有人可能會奇怪本次測試的意義?
一般服務(wù)器上會部署多個應(yīng)用 , 不會讓一個應(yīng)用把所有 CPU 都吃滿 , 而且一般線上應(yīng)用也不會是這么簡單的接口 , 瓶頸往往是在一些復(fù)雜的業(yè)務(wù)邏輯和數(shù)據(jù)庫上 , 網(wǎng)上也有提到三種容器使用的業(yè)務(wù)場景也有所不同逼龟。
我想說的是 , 本次測試純粹是測試容器的性能 , 空跑的結(jié)果都擺在這了 , 跑業(yè)務(wù)的話性能難道能更好评凝?如果 Jetty 真的如測試中那么差 , 還會有人用它跑高并發(fā)的項目嗎?
所以 , 本文只是提供一種測試思路 , 有疑問就多動動手進行論證 , 在沒有親自實踐過的情況下都不應(yīng)該輕易下"絕對"的結(jié)論腺律。