Quarkus 运行时性能
这是本系列博客的第一部分,深入探讨 Quarkus 的性能。一个框架的性能涉及许多方面,从启动时间到内存使用、编译时间和运行时性能。
“性能” 的定义是语境相关的,本系列博客旨在研究 Quarkus 在不同场景下的性能。
本文将重点关注使用 Quarkus 构建的应用程序的运行时性能。
简而言之 - 摘要
创建了一个从 PostgreSQL 数据库通过事务检索数据的 REST 应用程序,用于比较 Quarkus 和 Thorntail 的吞吐量和响应延迟。该应用程序在不同程度的负载下运行,以展示 Quarkus 的扩展能力。
与 Thorntail 单个进程相比,Quarkus 在原生模式下运行,支持 40 个并发连接,最大吞吐量提高了高达 38.4%,同时最大响应时间延迟降低了高达 42.7%。
与 Thorntail 单个进程相比,Quarkus 在 JVM 模式下运行,支持 40 个并发连接,最大吞吐量提高了高达 136%,同时最大响应时间延迟降低了高达 66.3%。
与原生模式相比,Quarkus 在 JVM 上运行时提供了更高的吞吐量和更快的响应时间,但使用了高达 277% 的内存 (RSS)。
对于在容器中运行、内存限制为 2048MB 的应用程序,通过运行多个 Quarkus 应用程序的 JVM 模式实例,理论上可以将应用程序吞吐量比 Thorntail 应用程序提高高达177.3%;通过运行多个 Quarkus 应用程序的原生模式实例,可以提高545%。
原生镜像不仅仅用于短期运行的进程。测试运行了长达 3 小时,没有进程重启,原生镜像处理了超过3300 万个请求!
没有一种万能的解决方案!Quarkus 允许您选择在 JVM 模式下进行纵向扩展,如果您需要单个具有更大堆的实例;或者在原生模式下进行横向扩展,如果您需要更多、更轻量级的实例。
关键问题
“优化启动时间和镜像大小固然好,但响应时间仍然很重要”.
让我们先来解决这个关键问题,Quarkus 到目前为止一直专注于启动时间和内存占用。
“那是因为原生性能很糟糕,对吧?错了!”
通过运行一个示例应用程序,该应用程序通过事务性 REST HTTP 请求从 PostgreSQL 数据库检索数据,我将讨论:
-
原生模式和 JVM 模式下的单进程吞吐量和响应时间,与 Thorntail 对比
-
长进程的原生镜像
应用程序和测试方法的详细信息可以在本文末尾的 测试应用程序 部分找到。
Quarkus 提供什么?
Quarkus 提供 2 种运行模式供您选择。您可以运行为原生二进制文件或在 JVM 上运行字节码。
这意味着您可以选择满足您的应用程序您的需求的运行时。如果原生镜像不能满足您的需求,没问题,选择您喜欢的 JVM。
但不要认为在 JVM 上运行是次等公民,Quarkus 针对在 JVM 上运行以及在原生模式下运行都进行了优化。
为什么与 Thorntail 进行比较?
Thorntail 是一个更传统的云原生栈,其基础来自 WildFly 社区,我们认为与我们知道如何优化的运行时进行比较是公平的。本次性能测试的目的不是进行框架与框架的比较,而是展示 Quarkus 所做的优化不仅仅局限于启动时间和初始内存消耗。Thorntail 是一个出色的运行时,但与其他传统的云原生栈一样,在独立部署时并不关注的运行时动态行为,在现代部署场景中却成为显著开销的原因。
吞吐量 (请求/秒)
最大吞吐量,以每秒请求数 (Req/Sec) 衡量,告诉我们单个进程应用程序每秒可以服务的最大请求数。最大吞吐量越高越好。
将原生 Quarkus 应用程序与运行在 JVM 上的 Thorntail 进行比较,随着并发用户数的增加,最大吞吐量保持一致。
Quarkus 0.18.0,在原生模式下运行单个实例,具有 40 个并发连接,与运行在 JVM 上的 Thorntail 2.4.0.Final 相比,最大吞吐量提高了 38.4%。. |
Quarkus 0.18.0,在 JVM 模式下运行单个实例,具有 40 个并发连接,与 Thorntail 2.4.0.Final 相比,吞吐量提高了 136%。. |

并发连接数 | Thorntail | Quarkus - 原生 | Quarkus - JVM |
---|---|---|---|
1 |
3,273 |
3,316 |
5,138 |
5 |
14,092 |
14,998 |
24,417 |
10 |
25,512 |
26,328 |
44,196 |
15 |
31,855 |
33,389 |
59,007 |
20 |
35,006 |
36,515 |
69,146 |
25 |
37,082 |
38,416 |
73,790 |
30 |
33,369 |
38,849 |
76,992 |
35 |
32,974 |
41,691 |
77,118 |
40 |
32,391 |
44,841 |
76,488 |
响应时间 (毫秒)
我想以“您所知道的关于延迟的一切都是错误的”这句话开始本节。[1]
响应时间衡量应用程序响应请求所需的时间。响应时间越低越好。但平均响应时间并不能全面反映应用程序的响应能力。最大响应时间比平均响应时间更能说明用户体验。
为什么这很重要?最大响应时间告诉我们最坏的情况,并且 26-93% 的页面加载将体验到 99% 的响应时间 [2]。拥有一个极低、极稳定的最大响应延迟会提高应用程序的响应能力。
在高并发用户数下;Quarkus 在 JVM 模式下的平均响应时间为 0.91 毫秒,而 Thorntail 为 1.69 毫秒。在原生模式下运行时,平均响应时间转变为 2.43 毫秒,以换取更低的内存利用率。
如果我们看最大响应时间;Thorntail 花费了 145.3 毫秒来服务至少一个请求,而 Quarkus JVM 为 65.01 毫秒,Quarkus Native 为 83.27 毫秒。
Quarkus 在原生模式下的最大响应时间非常稳定,比 Thorntail 低高达 42.7%。 |
在 JVM 上运行的更低的平均响应时间延迟是由于 JVM 中可用的 GC 实现优于 GraalVM 当前可用的 GC 实现。Quarkus 目前仍处于 Beta 发布阶段,并计划在原生模式下运行进行改进。 |


并发连接数 | Thorntail (平均) | Thorntail (最大) | Quarkus - 原生 (平均) | Quarkus - 原生 (最大) | Quarkus - JVM (平均) | Quarkus - JVM (最大) |
---|---|---|---|---|---|---|
1 |
0.324 |
9.31 |
0.327 |
6.13 |
0.196 |
9.52 |
5 |
0.461 |
13.12 |
0.494 |
9.86 |
0.232 |
13.85 |
10 |
0.53 |
11.3 |
0.698 |
14.24 |
0.278 |
16.08 |
15 |
0.842 |
145.16 |
0.91 |
14.86 |
0.334 |
18.38 |
20 |
1.02 |
134.9 |
1.15 |
16.4 |
0.389 |
23.7 |
25 |
1.2 |
145.3 |
1.3 |
16.86 |
0.472 |
21.25 |
30 |
1.26 |
34.87 |
1.69 |
26.52 |
0.545 |
83.27 |
35 |
1.35 |
30.94 |
1.84 |
65.01 |
0.78 |
32.9 |
40 |
1.69 |
143.49 |
2.43 |
48.37 |
0.91 |
63.71 |
应用程序启动时间
使用此处描述的方法测量了每个运行时的启动时间和内存使用情况:https://quarkus.net.cn/guides/performance-measure
指标 | Thorntail | Quarkus - 原生 | Quarkus - JVM |
---|---|---|---|
启动时间 |
8764 毫秒 |
18 毫秒 |
1629 毫秒 |
最大内存使用量
使用 ps
测量了每个应用程序进程的内存。
$ ps -o rss -p <PID>
捕获了运行期间的最大内存使用量。
Thorntail | Quarkus - JVM | Quarkus - 原生 |
---|---|---|
651 MB |
414 MB |
122 MB |
与 Thorntail 相比,原生模式下的 Quarkus 使用了仅18.7%的内存来处理多 20.9%的请求,而 JVM 模式下的 Quarkus 使用了63.6%的内存来处理多 108.0%的请求。 |
因此,使用具有 2048MB 内存的机器,运行多个进程(不受 CPU 限制),理论上可以比 Thorntail 提高以下吞吐量:
运行时模式 | 内存 (MB) | 每 2048MB 的进程数 | 每个进程的最大吞吐量 (请求/秒) | 总体最大吞吐量 (请求/秒) | 与 Thorntail 相比 |
---|---|---|---|---|---|
Thorntail |
651 |
3 |
37,082 |
111,246 |
100% |
Quarkus - JVM |
414 |
4 |
77,118 |
308,472 |
277% |
Quarkus - 原生 |
122 |
16 |
44,841 |
717,456 |
645% |
对于在云环境中运行的应用程序,通过运行多个 Quarkus 应用程序的原生模式实例,理论上可以将相同内存下的应用程序吞吐量提高高达545%。 |
Quarkus 原生 - 长进程
另一个担忧是 Quarkus 在原生模式下运行不适合长进程。
在测试期间,Quarkus 在原生模式下运行了超过 3 小时,并处理了超过51,890,000个请求! |
这些请求导致了数百次 Full GC 循环,但进程在此期间一直保持稳定。
测试应用程序
测试应用程序是一个事务性 REST/JPA 应用程序,它调用 PostgreSQL 数据库。应用程序和数据库都运行在 Docker 容器中。
构建和运行测试应用程序
构建;
Quarkus JVM
$ cd ./quarkus
$ build-quarkus-jvm.sh
或 Quarkus 原生
$ cd ./quarkus
$ build-quarkus-native.sh
或 Thorntail
$ cd ./thorntail
$ ./build-thorntail.sh
运行;
首先启动运行在 Docker 容器中的 PostgreSQL;
docker run -d --rm -p 5432:5432 --network host \
-e POSTGRES_DB='rest-crud' \
-e POSTGRES_USER='restcrud' \
-e POSTGRES_PASSWORD='restcrud' \
docker.io/postgres:10.5
然后启动运行在 Docker 容器中的应用程序;
$ cd ./quarkus
$ ./run-quarkus-jvm.sh
或 Quarkus 原生
$ run-quarkus-native.sh
或 Thorntail
$ cd ./thorntail
$ ./run-thorntail.sh
运行时验证
在浏览器中导航到 http://{REMOTE_HOST}:8080/
或
$ curl -D - http://{REMOTE_HOST}:8080/fruits
HTTP/1.1 200 OK
Connection: keep-alive
Content-Type: application/json
Content-Length: 75
Date: Mon, 01 Apr 2019 07:57:17 GMT
[{"id":2,"name":"Apple"},{"id":3,"name":"Banana"},{"id":1,"name":"Cherry"}]
运行时环境
被测系统
CPU: 32 x Intel® Xeon® CPU E5-2640 v3 @ 2.60GHz
操作系统: Red Hat Enterprise Linux Server release 7.6 (3.10.0-693.25.2.el7.x86_64)
内存: 262GB
以太网: Solarflare Communications SFC9020 10G 以太网控制器