背景
最近在做压测 , 同事觉得 Tomcat 不行想要切换 web 服务器 , 于是我就去网上搜了一些测评结果 , 不出意外 Tomcat 得到了一致的差评。抱着怀疑的态度 , 我决定对 SpringBoot 内嵌支持的三款 Servlet 容器 ( Tomcat 、Jetty 和 Undertow ) 做一个简单的对比测试 , 测试结果仅供参考 。
测试环境
项目 | 描述 |
---|---|
测试端主机 | 阿里云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 |
网络环境 | 阿里云专有网络 内网带宽: 2 Gbps |
测试工具 | wrk |
项目框架 | SpringBoot 1.5.10.RELEASE |
JDK | 1.8.0_191 Java HotSpot(TM) 64-Bit Server VM |
测试容器 | Tomcat Jetty Undertow |
说明:
测试端和被测端分别在两台主机上 , 为了避免放在一台主机上导致互相抢占 CPU 而无法得到真实结果。
主角是 Servlet容器 , 所以网络环境不应该是这次测试的瓶颈 ( 系统内核的 TCP 参数也要优化 ) 。
测试工具用过 wrk 、ab 、jmeter , 测试的目的是为了获取被测端的性能 , 但不能因为测试工具的性能瓶颈而埋没了被测端的真实能力 , 因此选用简单高效的 wrk ( 下载源码编译安装 ) 。
测试方法
使用最简单的 HTTP 接口 , 不包含任何业务逻辑和数据库操作 , 反映容器的极致性能
- 项目地址:https://github.com/anyesu/test/tree/master/servlet-test
@RestController
@RequestMapping("/api")
public class TestController {
@RequestMapping("/test")
public String test() {
return "This is a test result.";
}
}
使用 wrk 5000 并发下持续压测 10 分钟 ( 超时时间设为 30s , 避免超时时间过短带来的大量错误 )
./wrk -t 8 -c 5000 -d 10m -T 30s "http://yourhostip:3000/api/test"
说明:
JVM 内存设置为 2G 大小
项目启动后需要预热 ( JIT 编译、加线程等等 ) 一下才能达到最优的性能。
为了避免性能数据波动造成的影响 , 每种情况测试 3 遍后取最优结果。
按理说测试时间越长越能反映真实情况,不过试了 30 分钟的测试 , 一趟测试下来耗费了一天时间 , 结果和 10 分钟的也差不了多少 , 所以还是节省点时间吧。
测试结果
Tomcat 8.0.53 NIO 模式
-
启动命令:
java -Xms2g -Xmx2g -Xmn1g -XX:MaxMetaspaceSize=256m -Xss256k -jar servlet-test-tomcat-8-nio.jar
-
监控项:
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 结果:
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
-
监控项:
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 结果:
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 模式 ( 服务器上需要安装 apr 类库 )
-
启动命令:
java -Xms2g -Xmx2g -Xmn1g -XX:MaxMetaspaceSize=256m -Xss256k -Djava.library.path=/usr/local/apr/lib -jar servlet-test-tomcat-8-apr.jar
-
监控项:
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 结果:
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
-
监控项:
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 结果:
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
-
监控项:
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 结果:
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 配置线程数
-
启动命令:
java -Xms2g -Xmx2g -Xmn1g -XX:MaxMetaspaceSize=256m -Xss256k -Dserver.undertow.io-threads=16 -Dserver.undertow.worker-threads=256 -jar servlet-test-undertow.jar
-
监控项:
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 结果:
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
-
监控项:
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 结果:
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
测试结果分析
Servlet 容器 | QPS | QPS / GC 次数 |
---|---|---|
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 ( 最优 ) | 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 配置线程数 | 13980.36 | 13980.36 / 367 = 38.094 |
Undertow 2.0.15.Final | 13530.04 | 13530.04 / 361 = 37.479 |
先说结果: Tomcat APR 模式 "最优" , Jetty "最差" , Undertow 也没有想象中的碾压 Tomcat
Tomcat NIO 模式 压力刚上来的时候 CPU 会用满 , 稳定后基本会留有 10% 以上的空闲
Jetty 采用的默认配置 , 才 2k 不到的 QPS 确实有点出乎意料的 , 需要配置后做进一步的测试对比
Undertow 提供的配置项很少 , 只有 线程数 和 缓冲大小 , 加大 线程数 后也没能提升性能。本次测试中 Undertow2.0 版本也没有性能上的提升。
Tomcat8.5 相对于 Tomcat8.0 应该有性能上的提升 , 由于系统中安装的 apr 库的版本问题没有测试 Tomcat8.5+APR 的性能 ( Tomcat 版本和 apr 版本要对应 )
默认线程数: Undertow 为 8 ( CPU 核心数 ) 个 IO 线程 + 64 ( 8 * IO 线程数 ) 个 WORK 线程, Tomcat 和 Jetty 均在 200 左右。因为都用的 NIO , 所以调大线程数效果不大 , 反而带来 CPU上下文切换 和 内存消耗 ( -Xss ) 的问题
听人说 Undertow 在正常运行中会莫名其妙挂掉 , 具体原因还不清楚
优化
-
JVM 参数
曾经我也比较信奉所谓的 标准参数 , 然而按照上述方法单独对 Tomcat 进行对比测试后并没有得到所谓的 "参数调优" 的效果, 所以还是 针对实际运行情况再做调整吧。
主要几类配置项有:-server
内存 ( -Xms -Xmx -Xmn -Xss )
垃圾回收器组合
GC 相关参数 ( 打印日志 / DisableExplicitGC /ExplicitGCInvokesConcurrent / 定时 GC )
-
Connector 运行模式
Tomcat Connector 有 BIO 、NIO 、APR 三种运行模式 ( 可以看下这篇 文章 ) , 虽然 APR 模式性能最好 ( 不绝对 ) , 但也带来了维护上的成本 , 所以还是那句话 —— 针对实际的业务场景选择
-
项目运行参数
SpringBoot 中几个容器的可配置项都在 org.springframework.boot.autoconfigure.web.ServerProperties 这个类中, 一般默认就行了, 遇到问题再调整 ( Tomcat 相关配置可以看 这里 )
结语
-
本次测试结果和大部分网上的测试正好相反。
网上的测试:在一台机器上跑 , 用例并发量比较小 , 得到的 QPS 也比较低 ( 几百的数值而且比较接近 ) ,也没有说明测试使用的机器配置和容器版本 , 只是简单的把 Tomcat 归类为性能最差。这样的测试结论我认为是很不可靠的。
时间有限 , 以上只是部分的测试用例 , 实际可以组合的情况 ( 服务器配置、容器版本、实际业务代码等等 ) 多了去了 , 所以再强调一遍 —— 本次测试结果仅供参考 。
-
有人可能会奇怪本次测试的意义?
一般服务器上会部署多个应用 , 不会让一个应用把所有 CPU 都吃满 , 而且一般线上应用也不会是这么简单的接口 , 瓶颈往往是在一些复杂的业务逻辑和数据库上 , 网上也有提到三种容器使用的业务场景也有所不同。
我想说的是 , 本次测试纯粹是测试容器的性能 , 空跑的结果都摆在这了 , 跑业务的话性能难道能更好?如果 Jetty 真的如测试中那么差 , 还会有人用它跑高并发的项目吗?
所以 , 本文只是提供一种测试思路 , 有疑问就多动动手进行论证 , 在没有亲自实践过的情况下都不应该轻易下"绝对"的结论。