向英雄的前辈们致敬
每个旅程的起点 (Where each journey begins)
Let’s imagine for a while that you’re a Java team lead who is going to start a new project. A completely fresh and shiny, beginning from scratch. Cool feeling, right? Pretty interesting business domain, smart guys in the team and a client, who knows what is needed from the beginning. You feel like a hero who will change the world by implementing that using lingua franca in Java universe: Spring Boot. The confidence level is very high since it’s not the first project you’ve been implemented using this Swiss scissor. You know that whatever could you think of, it has been probably already implemented in this stack and just waiting for you to be used for free. It’s a good day you think — great project ahead with usual tooling. Almost the whole team seems to be satisfied with a potential tech stack choice. But there is the one sitting in the corner, who looks like being far from being happy…
让我们想象一下,您是Java团队的负责人,他将开始一个新项目。 从头开始,完全新鲜,有光泽。 很酷的感觉吧? 非常有趣的业务领域,团队中的聪明人和客户,他们从一开始就知道需要什么。 您感觉像是一位英雄,他将通过在Java Universe中使用lingua franca来实现世界,从而改变世界: Spring Boot 。 置信度很高,因为它不是您使用此瑞士剪刀实施的第一个项目。 您知道您能想到的是什么,它可能已经在此堆栈中实现,只是在等待您免费使用。 您认为这是美好的一天-出色的项目以及常规工具。 几乎整个团队似乎都对潜在的技术堆栈选择感到满意。 但是有一个坐在角落里的人,看起来好像离幸福还差得远……
新的酷孩子在街上 (New cool kids on the block)
His name is Joe. He just took part in one of those fancy Java conferences. And there he saw a presentation of another new and great framework: Quarkus. Probably all of you knows this feeling: a fascination, almost love and definitely the confidence that it beats everything that is currently available on the market. After 2 hours talk he became an evangelist of this new tool and he disagree with your initial choice. There are several points he’s mentioning: ease of developing due to the developer mode, nice set of out-of-the-box features implemented using curated Java technologies and what is the topping on a cake: very efficient resource utilization.
他叫乔。 他只是参加了其中一个花哨的Java会议。 在那儿,他看到了另一个新的出色框架的演示: Quarkus 。 也许所有人都知道这种感觉:一种迷恋,几乎是爱,并且绝对有信心击败它,超越了市场上现有的一切。 经过两个小时的交谈,他成为了这个新工具的推广者,他不同意您的最初选择。 他提到了几点:开发人员模式简化了开发,使用了精选的Java技术实现了一系列出色的现成功能,而且最重要的是:非常高效的资源利用。
So there is a scratch on the surface of your perfect plan. The team seems to be confused and they are waiting for reaction of the team lead. But you’ve been there already. It’s not the first time when you see a new knight in the castle who is shouting: ‘Hail to the new king’. But you know what should be done next: it’s neither sticking to the well-proven stuff nor blindly following a new wave and betting all-in to for a new contestant. Your call is to…
因此,您的完美计划表面有一个划痕。 团队似乎很困惑,他们正在等待团队领导的React。 但是你已经去过那里了。 这不是您第一次在城堡中看到一个新的骑士喊着:“向新国王欢呼”。 但是您知道下一步应该做什么:它既不会坚持久经考验的工作,也不会盲目跟随新的潮流并全力以赴押注新的参赛者。 你的电话是...
基准测试 (Benchmark)
There is no way to judge the developer experience within a short period of time. You definitely have to work with a tool much longer to see whether you like it or not. Initial positive impressions might be ruined after first issues that requires spending a lot of time to analyze them. You know it can’t be a part of your benchmark since it’s very subjective too.
无法在短时间内判断开发人员的经验。 您肯定需要使用工具更长的时间才能看到您是否喜欢它。 最初的正面印象可能会在需要大量时间来分析它们的第一个问题之后被破坏。 您知道它不能成为基准测试的一部分,因为它也是非常主观的。
So you’re thinking about the next thing mentioned by Joe: standardized libraries. You’re looking at Quarkus: ok, there is a lot of good stuff in there. On the other hand there is Spring Boot which de facto is a standard itself. After a while you’re realize the tooling is very similar and there are a lot of common libraries used by both of those frameworks. We have a tie in here, so we’re going next.
因此,您正在考虑Joe提到的下一件事:标准化库。 您正在看Quarkus:好的,里面有很多好东西。 另一方面,Spring Boot本身实际上是一个标准。 一段时间后,您意识到工具非常相似,并且这两个框架都使用了许多通用库。 我们在这里有关系,所以我们下一步。
Last try: performance and resource utilization. This seems to be a good candidate. First of all during the kick-off you heard that solution performance is very important for the business. As always resources (CPU and memory) are implicitly important (more required resources = more money spent. There is no client in the world who says: max everything for my small project; I don’t care with costs). So you’ve nailed it: let’s check performance using simple load tests.
最后尝试:性能和资源利用率。 这似乎是一个很好的候选人。 首先,在启动过程中,您听说了解决方案性能对业务非常重要。 与往常一样,资源(CPU和内存)是非常重要的(需要更多的资源=花更多的钱。世界上没有客户会说:为我的小项目最大程度地利用一切;我不在乎成本)。 因此,您已将其钉牢:让我们使用简单的负载测试来检查性能。
应用功能 (Application features)
So now you’re thinking about how to implement a sample application for benchmark. For sure you would like to see some I/O performance since the new app will heavily rely on a database. Also you would like to see how it behaves with regular, blocking method calls, so you’re adding this to your list too. Of course you have to expose an application functionality using REST calls. Also each application have to be Dockerized since a deployment will took place on a Kubernetes cluster. It seems that for now the list is completed and you’re good to go.
因此,现在您正在考虑如何实现示例应用程序以进行基准测试。 可以肯定的是,由于新应用程序将严重依赖数据库,因此您希望看到一些I / O性能。 另外,您还想了解它在常规的阻塞方法调用中的行为,因此也将其添加到列表中。 当然,您必须使用REST调用公开应用程序功能。 另外,每个应用程序都必须进行Docker化,因为部署将在Kubernetes集群上进行。 看来目前清单已完成,您很高兴。
This is a sample implementation of your idea using Spring Boot (all other implementations can be found in this Github repository:
这是使用Spring Boot实现的想法的示例实现(所有其他实现都可以在此Github存储库中找到:
@RestController
@RequiredArgsConstructor
public class ExampleController {
private final ExampleService service;
@GetMapping(path = "/api/hello")
public Message hello() {
return service.blockingHello();
}
@GetMapping(path = "/api/cities")
public Cities cityByCountryCode(@RequestParam("country_code") final String countryCode) {
return service.findCitiesByCountryCode(countryCode);
}
}@Service
@RequiredArgsConstructor
public class ExampleService {
private final CityRepository repository;
@SneakyThrows
public Message blockingHello() {
Thread.sleep(100);
return new Message("Hello!");
}
public Cities findCitiesByCountryCode(final String countryCode) {
var cities = repository.findAllByCountryCode(countryCode).stream()
.map(City::from)
.collect(Collectors.toList());
return new Cities(cities);
}
}
竞争者 (Competitors)
There are two main competitors in this clash: Spring Boot and Quarkus. But as always the world is not that simple. There are blocking implementations and reactive ones (for Spring Boot we’ve got Webflux, for Quarkus there is Mutiny) in each of them. Another thing are Docker flavors: besides regular JVM Quarkus team is offering native image basing on GraalVM.
在这场冲突中,有两个主要竞争对手:Spring Boot和Quarkus。 但是,与以往一样,世界并非如此简单。 每个实现中都有阻塞的实现和React性的实现(对于Spring Boot,我们有Webflux,对于Quarkus,有Mutiny)。 另一件事是Docker风格:除了常规的JVM Quarkus团队,它还基于GraalVM提供本机映像。
Having that in mind you’ve ended with the following list of competitors:
考虑到这一点,您已经结束了以下竞争对手的列表:
Quarkus + Jackson + JVM (alias
quarkus-jvm
)Quarkus + Jackson + JVM(别名
quarkus-jvm
)Quarkus + Jackson + native image (alias
quarkus-native
)Quarkus + Jackson +本机映像(别名
quarkus-native
)Quarkus + Mutiny + Jackson + JVM (alias
quarkus-mutiny-jvm
)Quarkus + Mutiny + Jackson + JVM(别名
quarkus-mutiny-jvm
)Quarkus + Mutiny + Jackson + native image (alias
quarkus-mutiny-native
)Quarkus + Mutiny + Jackson +本机映像(别名
quarkus-mutiny-native
)Spring Boot + Jackson + JVM (alias
spring-boot-jvm
)Spring Boot + Jackson + JVM(别名
spring-boot-jvm
)Spring Boot + Webflux + Jackson + JVM (alias
spring-boot-webflux-jvm
)Spring Boot + Webflux + Jackson + JVM(别名
spring-boot-webflux-jvm
)
Each application was already built and has a Docker image available under this repository
每个应用程序均已构建,并在此存储库下具有一个Docker映像
基准情景 (Benchmark scenario)
Gatling was used as a load testing tool. Each testing round consists of 2 strategies of load generation:
加特林被用作负载测试工具。 每个测试回合都包含2种负载生成策略:
- 5 req/s during 10 minutes 10分钟内5个请求/秒
- ramping to 8 req/s during 20 minutes 在20分钟内上升到8 req / s
The following code snippet shows the scenario (really basic one, every user just queries two available endpoints).
下面的代码片段显示了这种情况(实际上是一种基本情况,每个用户仅查询两个可用的端点)。
class QuarkusVsSpringBootBenchmark extends Simulation {
private val warmupStrategy = List(
constantUsersPerSec(1) during (3 minute)
)
private val constantUsersStrategy = List(
constantUsersPerSec(5) during (10 minutes)
)
private val spikeStrategy = List(
rampUsersPerSec(0) to 8 during (20 minutes)
)
private val loadStrategies = Map(
"WARMUP" -> warmupStrategy,
"CONSTANT" -> constantUsersStrategy,
"SPIKE" -> spikeStrategy
)
// rest of the setup ommitted
...
val scn: ScenarioBuilder = scenario("Benchmark")
.repeat(scenarioRepeatCount) {
exec(http("/hello")
.get("/hello"))
.pause(3)
.exec(http("/cities")
.get("/cities?country_code=NLD"))
.pause(2)
}
setUp(scn.inject(loadStrategies.getOrElse(strategyName, warmupStrategy)).protocols(httpProtocol))
}
Also there is a bash
script provided for starting Gatling with different parameters. Each script run was treated as a test round (for results warmup
run is excluded).
还提供了一个bash
脚本,用于使用不同的参数启动加特林。 每次脚本运行均被视为测试回合(不包括结果warmup
运行)。
#!/bin/bash
// variables setup ommitted
...
echo "Running warmup..."
export BENCHMARK_STRATEGY_NAME=SINGLE
./bin/gatling.sh -rd "warmup ${image_tag}"
echo "Running test 1/2..."
export BENCHMARK_STRATEGY_NAME=CONSTANT
./bin/gatling.sh -rd "test 1/2 ${image_tag}"
echo "Running test 2/2..."
export BENCHMARK_STRATEGY_NAME=SPIKE
./bin/gatling.sh -rd "test 2/2 ${image_tag}"
开演时间 (Show time)
The whole benchmarking scenario took place on Kubernetes cluster hosted on GCP (3x nodes with the cheapest N1
instance type). Gatling was running on a separate VM (cheapest N2
instance) hosted in the same zone as the cluster. The database was hosted on the Heroku (free plan) just to spice things up and add some latency. Pods were exposed by a service of a type LoadBalancer
. Between each round resource limits were adjusted accordingly.
整个基准测试场景发生在GCP托管的Kubernetes集群上(3x节点的N1
实例类型最便宜)。 Gatling在与群集位于同一区域中托管的单独VM(最便宜的N2
实例)上运行。 该数据库托管在Heroku(免费计划)上,目的只是为了增加趣味性并增加一些延迟。 通过类型为LoadBalancer
的服务暴露了豆荚。 在每一轮之间,对资源限制进行了相应调整。
The tables below consist only the best results (lowest p99
) of each round for each application. The full lists of results (each round were executed at least 2 times for each application) can be found in Github repository.
下表仅列出每种应用中每一轮的最佳结果(最低p99
)。 完整的结果列表(每个应用程序至少执行了两轮)可以在Github存储库中找到。
第1轮 (Round #1)
Initial setup, rather not optimized for an application with such limited functionality. What’s important: for Spring Boot probes delays have to be extended due to a slower time for a first response.
初始设置,而不是针对功能受限的应用程序进行了优化。 重要的是:对于Spring Boot探针,由于第一次响应的时间较慢,因此必须延长延迟。

Constant load — /hello
恒定负载— /hello
Constant load — /cities
恒定负载— /cities
Ramp up load — /hello
加速加载- /hello
Ramp up load — /cities
加速负荷- /cities
Thoughts: all candidates looks pretty similar. A big surprise for me is performance of Spring Boot Webflux in ramp scenarios (both I/O and not I/O related endpoints have problems with responsiveness which can definitely exclude network problems related with database communication).
想法:所有候选人看上去都很相似。 给我一个很大的惊喜是Spring Boot Webflux在渐变场景下的性能(I / O和与I / O不相关的端点都有响应性问题,这些问题肯定可以排除与数据库通信有关的网络问题)。
第二回合 (Round #2)
Let’s rise the bar a bit higher and reduce available memory a bit. This resulted in further extension of probes delay for Spring Boot.
让我们将标准提高一点,并减少一些可用内存。 这进一步延长了Spring Boot的探测延迟。

Constant load — /hello
恒定负载— /hello
Constant load — /cities
恒定负载— /cities
Ramp up load — /hello
加速加载- /hello
Ramp up load — /cities
加速负荷- /cities
Thoughts: this is the place where old champion has failed. Kicked out Spring Boot requests were caused by pods OutOfMemory error and an infinitive restarts loop. For Quarkus cases it’s pretty surprising that non-blocking implementation (which uses reactive Postgres driver under the hood) seems to be slower comparing to the regular one.
思考:这是老冠军失败的地方。 Spring Boot请求被踢出是由于Pod OutOfMemory错误和无限的重启循环引起的。 对于Quarkus而言,非阻塞实现(在幕后使用React式Postgres驱动程序)似乎比常规实现慢得多。
第三回合 (Round #3)
Spring Boot is dropped due to the results from the last round. Rest of the competitors will be tested with CPU request reduced by a half.
由于上一轮的结果,Spring Boot被删除。 其他竞争者将在CPU请求减少一半的情况下进行测试。

Constant load — /hello
恒定负载— /hello
Constant load — /cities
恒定负载— /cities
Ramp up load — /hello
加速加载- /hello
Ramp up load — /cities
加速负荷- /cities
Thoughts: very similar results of all Quarkus based applications. Again Mutiny based implementations seems to be slower than the regular ones.
想法:所有基于Quarkus的应用程序的结果都非常相似。 同样,基于Mutiny的实现似乎比常规实现慢。
第四回合 (Round #4)
The most requiring environment for the competitors. Both JVM-based Quarkus apps were dropped due to the startup issues (they were not able to start within the limited probes delay; for a potential new king there were no mercy and delays were not extended).
竞争对手最需要的环境。 由于启动问题,两个基于JVM的Quarkus应用程序均被删除(它们无法在有限的探测延迟内启动;对于潜在的新国王,他们不会宽容,延迟也不会延长)。

Constant load — /hello
恒定负载— /hello
Constant load — /cities
恒定负载— /cities
Ramp up load — /hello
加速加载- /hello
Ramp up load — /cities
加速负荷- /cities
Thoughts: last round, last two competitors. This time the Mutiny based implementation wins and passes the test.
思考:最后一轮,最后两个竞争对手。 这次基于Mutiny的实现胜出并通过了测试。
那么现在怎么办? (So now what?)
Results are pretty obvious: in terms of the raw performance and resources utilization there is only one winner. Due to a completely different approach in the implementation of frameworks internals (most of the expensive stuff is done during a compilation instead of runtime backed by multiple proxies) it can lower a bar a lot when it comes to CPU and memory requirements without a compromise on performance. Also ease of native image creation (when you stick with the modules offered by the Quarkus team; otherwise it can be very tricky with third-party dependencies. Also you have to have it might be very time-consuming to create one) shows a great potential for micro-services implementation. You can gain a lot if your workloads require containers to dynamically scale up and down depending on the traffic.
结果非常明显:就原始性能和资源利用率而言,只有一个赢家。 由于框架内部实现的方法完全不同(大多数昂贵的东西是在编译过程中完成的,而不是由多个代理支持的运行时),因此在降低CPU和内存要求时,可以大幅度降低标准性能。 还可以简化本机映像的创建(当您坚持使用Quarkus团队提供的模块时;否则,使用第三方依赖项可能会非常棘手。另外,您必须非常费时地创建一个),这表明了微服务实施的潜力。 如果您的工作量需要容器根据流量动态扩展和缩小,那么您将获得很多收益。
但这是灵丹妙药吗? (But is it a silver bullet?)
One might think now that Spring Boot should be forgotten and abandoned as e.g Java EE. But this opinion would be completely wrong. Raw performance definitely is not the only property to evaluate when it comes to a tech stack selection. Quarkus is still something new and there are many missing pieces there that are available in Spring out-of-the-box (cache implementation using Redis as a store to quickly name one that I was missing). Also some libraries used by Quarkus (e.g: RESTeasy) might not be known by many Java developers.
也许有人会想到,Spring Boot应该像Java EE这样被遗忘和抛弃。 但是这种观点是完全错误的。 原始性能绝对不是评估技术堆栈选择时唯一要评估的属性。 Quarkus仍然是新事物,Spring可以立即使用许多缺失的部分(使用Redis作为存储的缓存实现以快速命名我所缺少的部分)。 而且,许多Java开发人员可能不了解Quarkus使用的某些库(例如:RESTeasy)。
Spring Boot is also great when it comes to micro-services development and testing (many battle-proven ready to go modules and integrations like Eureka, Feign etc. and great tooling like Spring Cloud Contract). Quarkus, although developed very actively, can’t catch up with years of Spring ecosystem development within a seconds.
当涉及到微服务开发和测试时,Spring Boot也很棒(许多经过实践验证的现成可用的模块和集成,例如Eureka,Feign等,以及出色的工具,例如Spring Cloud Contract)。 尽管Quarkus开发非常活跃,但它在一秒钟内无法赶上Spring生态系统多年的开发。
You should not follow hype but precisely evaluate the goals you want to achieve and constraints you’re working with.
您不应该大肆宣传,而应准确评估您要实现的目标和使用的约束。
When it comes to a technology selection it’s always about architecture drivers relevant for a particular project. You should not follow hype but precisely evaluate the goals you want to achieve and constraints you’re working with. As always there is no silver bullet that will solve all your problems. Use your experience and judge what’s more important for you: if your main concern is a raw performance then you should give Quarkus a shot. If you can benefit from its startup time (especially in native mode) then it’s definitely worth trying. But be aware it might not be easy: there might be many pieces missing that existence were obvious in Spring’s world. On the other hand: when you can make a compromise and sacrifice a bit lower resources utilization and get a rich and battle-proven ecosystem of modules and integrations (which means faster time to market) instead of it then Spring Boot is still something great.
在选择技术时,总是与特定项目相关的体系结构驱动程序有关。 您不应该大肆宣传,而应准确评估您要实现的目标和使用的约束。 与往常一样,没有解决所有问题的灵丹妙药。 利用您的经验并判断对您而言更重要的是:如果您主要关注的是原始表现,那么您应该给Quarkus一个机会。 如果您可以从其启动时间中受益(尤其是在纯模式下),那么绝对值得尝试。 但是要知道,这可能并不容易:可能存在许多遗失之处,因为存在在Spring的世界中显而易见。 另一方面:当您可以做出妥协并牺牲较低的资源利用率,并获得一个经过实践检验的丰富的模块和集成生态系统(这意味着更快的上市时间)时,Spring Boot仍然很棒。
PS.: Joe was not able to convince the team to his newest and greatest idea. They decided to go with the Spring Boot at the beginning and leave the doors for eventual future migration. Joe was so sad that he can’t experiment spending customers money that he decided to leave the company and try CV driven development somewhere else.
PS .: Joe无法说服团队采用他最新和最伟大的想法。 他们决定从一开始就使用Spring Boot,并为将来的最终迁移留出了大门。 乔很伤心,以至于无法尝试花钱给客户,于是决定离开公司,在其他地方尝试CV驱动的开发。
PS 2.: Try not to behave as Joe. Please experiment in your free time instead of risking others money :)
PS 2:尽量不要表现得像乔。 请尝试您的空闲时间,而不要冒险让别人冒险:)
翻译自: https://medium.com/swlh/hail-to-the-new-king-or-not-295090a96bbf
向英雄的前辈们致敬