背景
最近在做壓測(cè) , 同事覺(jué)得 Tomcat 不行想要切換 web 服務(wù)器 , 于是我就去網(wǎng)上搜了一些測(cè)評(píng)結(jié)果 , 不出意外 Tomcat 得到了一致的差評(píng)。抱著懷疑的態(tài)度 , 我決定對(duì) SpringBoot 內(nèi)嵌支持的三款 Servlet 容器 ( Tomcat 、Jetty 和 Undertow ) 做一個(gè)簡(jiǎn)單的對(duì)比測(cè)試 , 測(cè)試結(jié)果僅供參考 。
測(cè)試環(huán)境
| 項(xiàng)目 | 描述 |
|---|---|
| 測(cè)試端主機(jī) | 阿里云ECS(ecs.sn1ne.2xlarge) 8C16G CentOS7.4.1708 Linux Kernel 3.10.0
|
| 被測(cè)端主機(jī) | 阿里云ECS(ecs.sn1ne.2xlarge) 8C16G CentOS7.4.1708 Linux Kernel 3.10.0
|
| 網(wǎng)絡(luò)環(huán)境 | 阿里云專(zhuān)有網(wǎng)絡(luò) 內(nèi)網(wǎng)帶寬: 2 Gbps |
| 測(cè)試工具 | wrk |
| 項(xiàng)目框架 | SpringBoot 1.5.10.RELEASE |
| JDK |
1.8.0_191 Java HotSpot(TM) 64-Bit Server VM
|
| 測(cè)試容器 |
Tomcat Jetty Undertow
|
說(shuō)明:
測(cè)試端和被測(cè)端分別在兩臺(tái)主機(jī)上 , 為了避免放在一臺(tái)主機(jī)上導(dǎo)致互相搶占 CPU 而無(wú)法得到真實(shí)結(jié)果。
主角是 Servlet容器 , 所以網(wǎng)絡(luò)環(huán)境不應(yīng)該是這次測(cè)試的瓶頸 ( 系統(tǒng)內(nèi)核的 TCP 參數(shù)也要優(yōu)化 ) 。
測(cè)試工具用過(guò) wrk 、ab 、jmeter , 測(cè)試的目的是為了獲取被測(cè)端的性能 , 但不能因?yàn)闇y(cè)試工具的性能瓶頸而埋沒(méi)了被測(cè)端的真實(shí)能力 , 因此選用簡(jiǎn)單高效的 wrk ( 下載源碼編譯安裝 ) 。
測(cè)試方法
使用最簡(jiǎn)單的 HTTP 接口 , 不包含任何業(yè)務(wù)邏輯和數(shù)據(jù)庫(kù)操作 , 反映容器的極致性能
@RestController
@RequestMapping("/api")
public class TestController {
@RequestMapping("/test")
public String test() {
return "This is a test result.";
}
}
使用 wrk 5000 并發(fā)下持續(xù)壓測(cè) 10 分鐘 ( 超時(shí)時(shí)間設(shè)為 30s , 避免超時(shí)時(shí)間過(guò)短帶來(lái)的大量錯(cuò)誤 )
./wrk -t 8 -c 5000 -d 10m -T 30s "http://yourhostip:3000/api/test"
說(shuō)明:
JVM 內(nèi)存設(shè)置為 2G 大小
項(xiàng)目啟動(dòng)后需要預(yù)熱 ( JIT 編譯、加線程等等 ) 一下才能達(dá)到最優(yōu)的性能。
為了避免性能數(shù)據(jù)波動(dòng)造成的影響 , 每種情況測(cè)試 3 遍后取最優(yōu)結(jié)果。
按理說(shuō)測(cè)試時(shí)間越長(zhǎng)越能反映真實(shí)情況,不過(guò)試了 30 分鐘的測(cè)試 , 一趟測(cè)試下來(lái)耗費(fèi)了一天時(shí)間 , 結(jié)果和 10 分鐘的也差不了多少 , 所以還是節(jié)省點(diǎn)時(shí)間吧。
測(cè)試結(jié)果
Tomcat 8.0.53 NIO 模式
-
啟動(dòng)命令:
java -Xms2g -Xmx2g -Xmn1g -XX:MaxMetaspaceSize=256m -Xss256k -jar servlet-test-tomcat-8-nio.jar -
監(jiān)控項(xiàng):
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 模式
-
啟動(dòng)命令:
java -Xms2g -Xmx2g -Xmn1g -XX:MaxMetaspaceSize=256m -Xss256k -jar servlet-test-tomcat-8.5-nio.jar -
監(jiān)控項(xiàng):
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 類(lèi)庫(kù) )
-
啟動(dòng)命令:
java -Xms2g -Xmx2g -Xmn1g -XX:MaxMetaspaceSize=256m -Xss256k -Djava.library.path=/usr/local/apr/lib -jar servlet-test-tomcat-8-apr.jar -
監(jiān)控項(xiàng):
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
-
啟動(dòng)命令:
java -Xms2g -Xmx2g -Xmn1g -XX:MaxMetaspaceSize=256m -Xss256k -jar servlet-test-jetty.jar -
監(jiān)控項(xiàng):
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
-
啟動(dòng)命令:
java -Xms2g -Xmx2g -Xmn1g -XX:MaxMetaspaceSize=256m -Xss256k -jar servlet-test-undertow.jar -
監(jiān)控項(xiàng):
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ù)
-
啟動(dòng)命令:
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)控項(xiàng):
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
-
啟動(dòng)命令:
java -Xms2g -Xmx2g -Xmn1g -XX:MaxMetaspaceSize=256m -Xss256k -jar servlet-test-undertow2.jar -
監(jiān)控項(xiàng):
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
測(cè)試結(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 |
先說(shuō)結(jié)果: Tomcat APR 模式 "最優(yōu)" , Jetty "最差" , Undertow 也沒(méi)有想象中的碾壓 Tomcat
Tomcat NIO 模式 壓力剛上來(lái)的時(shí)候 CPU 會(huì)用滿 , 穩(wěn)定后基本會(huì)留有 10% 以上的空閑
Jetty 采用的默認(rèn)配置 , 才 2k 不到的 QPS 確實(shí)有點(diǎn)出乎意料的 , 需要配置后做進(jìn)一步的測(cè)試對(duì)比
Undertow 提供的配置項(xiàng)很少 , 只有 線程數(shù) 和 緩沖大小 , 加大 線程數(shù) 后也沒(méi)能提升性能。本次測(cè)試中 Undertow2.0 版本也沒(méi)有性能上的提升。
Tomcat8.5 相對(duì)于 Tomcat8.0 應(yīng)該有性能上的提升 , 由于系統(tǒng)中安裝的 apr 庫(kù)的版本問(wèn)題沒(méi)有測(cè)試 Tomcat8.5+APR 的性能 ( Tomcat 版本和 apr 版本要對(duì)應(yīng) )
默認(rèn)線程數(shù): Undertow 為 8 ( CPU 核心數(shù) ) 個(gè) IO 線程 + 64 ( 8 * IO 線程數(shù) ) 個(gè) WORK 線程, Tomcat 和 Jetty 均在 200 左右。因?yàn)槎加玫?NIO , 所以調(diào)大線程數(shù)效果不大 , 反而帶來(lái) CPU上下文切換 和 內(nèi)存消耗 ( -Xss ) 的問(wèn)題
聽(tīng)人說(shuō) Undertow 在正常運(yùn)行中會(huì)莫名其妙掛掉 , 具體原因還不清楚
優(yōu)化
-
JVM 參數(shù)
曾經(jīng)我也比較信奉所謂的 標(biāo)準(zhǔn)參數(shù) , 然而按照上述方法單獨(dú)對(duì) Tomcat 進(jìn)行對(duì)比測(cè)試后并沒(méi)有得到所謂的 "參數(shù)調(diào)優(yōu)" 的效果, 所以還是 針對(duì)實(shí)際運(yùn)行情況再做調(diào)整吧。
主要幾類(lèi)配置項(xiàng)有:-server
內(nèi)存 ( -Xms -Xmx -Xmn -Xss )
GC 相關(guān)參數(shù) ( 打印日志 / DisableExplicitGC /ExplicitGCInvokesConcurrent / 定時(shí) GC )
-
Connector 運(yùn)行模式
Tomcat Connector 有 BIO 、NIO 、APR 三種運(yùn)行模式 ( 可以看下這篇 文章 ) , 雖然 APR 模式性能最好 ( 不絕對(duì) ) , 但也帶來(lái)了維護(hù)上的成本 , 所以還是那句話 —— 針對(duì)實(shí)際的業(yè)務(wù)場(chǎng)景選擇
-
項(xiàng)目運(yùn)行參數(shù)
SpringBoot 中幾個(gè)容器的可配置項(xiàng)都在 org.springframework.boot.autoconfigure.web.ServerProperties 這個(gè)類(lèi)中, 一般默認(rèn)就行了, 遇到問(wèn)題再調(diào)整 ( Tomcat 相關(guān)配置可以看 這里 )
結(jié)語(yǔ)
-
本次測(cè)試結(jié)果和大部分網(wǎng)上的測(cè)試正好相反。
網(wǎng)上的測(cè)試:在一臺(tái)機(jī)器上跑 , 用例并發(fā)量比較小 , 得到的 QPS 也比較低 ( 幾百的數(shù)值而且比較接近 ) ,也沒(méi)有說(shuō)明測(cè)試使用的機(jī)器配置和容器版本 , 只是簡(jiǎn)單的把 Tomcat 歸類(lèi)為性能最差。這樣的測(cè)試結(jié)論我認(rèn)為是很不可靠的。
時(shí)間有限 , 以上只是部分的測(cè)試用例 , 實(shí)際可以組合的情況 ( 服務(wù)器配置、容器版本、實(shí)際業(yè)務(wù)代碼等等 ) 多了去了 , 所以再?gòu)?qiáng)調(diào)一遍 —— 本次測(cè)試結(jié)果僅供參考 。
-
有人可能會(huì)奇怪本次測(cè)試的意義?
一般服務(wù)器上會(huì)部署多個(gè)應(yīng)用 , 不會(huì)讓一個(gè)應(yīng)用把所有 CPU 都吃滿 , 而且一般線上應(yīng)用也不會(huì)是這么簡(jiǎn)單的接口 , 瓶頸往往是在一些復(fù)雜的業(yè)務(wù)邏輯和數(shù)據(jù)庫(kù)上 , 網(wǎng)上也有提到三種容器使用的業(yè)務(wù)場(chǎng)景也有所不同。
我想說(shuō)的是 , 本次測(cè)試純粹是測(cè)試容器的性能 , 空跑的結(jié)果都擺在這了 , 跑業(yè)務(wù)的話性能難道能更好?如果 Jetty 真的如測(cè)試中那么差 , 還會(huì)有人用它跑高并發(fā)的項(xiàng)目嗎?
所以 , 本文只是提供一種測(cè)試思路 , 有疑問(wèn)就多動(dòng)動(dòng)手進(jìn)行論證 , 在沒(méi)有親自實(shí)踐過(guò)的情況下都不應(yīng)該輕易下"絕對(duì)"的結(jié)論。