SpringBoot性能比较:Spring MVC与WebFlux

栏目: Java · 发布时间: 7年前

内容简介:在这里我想谈谈曾经在项目中遇到的有趣的事情。我们为我们的客户在AWS中编写了一些轻量级微服务,它只是通过HTTP代理对某些底层服务的请求,并将其返回给客户端。乍一看,什么可能比编写REST代理服务更简单?所以,当然,我们从Spring Boot开始编写简单的RestControllers。我们做了POC,结果很好。第三方服务具有符合要求的服务响应时间SLA,我们使用此值进行性能测试,第三方服务的响应时间非常好〜大约10-100ms。我们还决定利用CPU作为我们的微服务的扩展策略,这个服务是在Docker中作

在这里我想谈谈曾经在项目中遇到的有趣的事情。我们为我们的客户在AWS中编写了一些轻量级微服务,它只是通过HTTP代理对某些底层服务的请求,并将其返回给客户端。

乍一看,什么可能比编写REST代理服务更简单?

所以,当然,我们从Spring Boot开始编写简单的RestControllers。我们做了POC,结果很好。第三方服务具有符合要求的服务响应时间SLA,我们使用此值进行性能测试,第三方服务的响应时间非常好〜大约10-100ms。我们还决定利用CPU作为我们的微服务的扩展策略,这个服务是在 Docker 中作为AWS ECS服务运行。我们在AWS中配置了自动扩展并上线了。

事实上,并非一切顺利。运行经常超时,我们经常重启AWS ECS任务。我们只是运行很少的任务,另外,看到CPU和内存消耗很低但我们的服务还是太慢,有时甚至有超时错误。

问题在于第三方服务。第三方服务响应时间变为500-1000ms。但它从来没有超时问题,能够处理更多的客户端。

所以关键问题还是在于我们的服务。我们没有在需要时扩展我们的应用程序。我们进行了500-1000毫秒的性能测试,并感到震惊。

CPU很低,内存很好,但我们只能处理200个请求/秒。

这是Servlet线程的连接问题,默认线程池是200,这就是为什么我们在1000毫秒响应时间内有200个请求/秒的原因。

但我们需要一个弹性服务:我们应该处理与底层服务一样多的请求。响应时间应与基础服务几乎相同。

我们研究它并找到了几个选项:

  1. 增加线程池大小
  2. 使用Servlet的DeferredResult或CompletableFuture
  3. Spring与WebFlux反应

选项1:增加线程池大小

是的,这是一个很好的解决方法,但只是解决方法!我们不能将这个值设置为几千,因为它是具有非常有限的内存的Docker。每个线程都需要堆栈内存。

另一个问题是,如果某些第三方服务的响应时间很长,例如,5秒,我们仍会遇到同样的问题。吞吐量等于=线程池大小/响应时间。如果我们有1000个线程和5秒延迟,则吞吐量是200个请求/秒。CPU再次很低,服务有足够的资源进行处理。

选项2:带Servlet的DeferredResult或CompletableFurure(非阻塞)

Servlet 3.1支持异步处理。为了使它工作,我们需要返回一些Promise,Servlet将以异步方式处理它。

我们将DeferredResult与CompletableFurure进行了比较,结果相同。因此,我们同意测试CompletableFurure。

选项3:Spring与WebFlux反应

这是现在最热门的话题。从Spring 文档

“使用少量线程处理并发性并使用更少的硬件资源进行扩展的非阻塞Web堆栈”

测试

测试环境:

Spring Boot:2.1.2.RELEASE(最新)

Java:11 OpenJDK

节点:t2.micro(亚马逊Linux)

代码: https //github.com/Aleksandr-Filichkin/spring-mvc-vs-webflux

Http客户端: Java 11 Http客户端,Apache Http客户端,Spring WebClient

Test-Service(我们的代理服务)公开了几个GET端点进行测试。所有端点都有一个延迟(以毫秒为单位)参数,用于模拟第三方服务延迟。

@GetMapping(value = <font>"/sync"</font><font>)
<b>public</b> String getUserSync(@RequestParam <b>long</b> delay) {
    <b>return</b> sendRequestWithJavaHttpClient(delay).thenApply(x -> </font><font>"sync: "</font><font> + x).join();
}
@GetMapping(value = </font><font>"/completable-future-java-client"</font><font>)
<b>public</b> CompletableFuture<String> getUserUsingWithCFAndJavaClient(@RequestParam <b>long</b> delay) {
    <b>return</b> sendRequestWithJavaHttpClient(delay).thenApply(x -> </font><font>"completable-future-java-client: "</font><font> + x);
}
@GetMapping(value = </font><font>"/completable-future-apache-client"</font><font>)
<b>public</b> CompletableFuture<String> getUserUsingWithCFAndApacheCLient(@RequestParam <b>long</b> delay) {
    <b>return</b> sendRequestWithApacheHttpClient(delay).thenApply(x -> </font><font>"completable-future-apache-client: "</font><font> + x);
}
@GetMapping(value = </font><font>"/webflux-java-http-client"</font><font>)
<b>public</b> Mono<String> getUserUsingWebfluxJavaHttpClient(@RequestParam <b>long</b> delay) {
    CompletableFuture<String> stringCompletableFuture = sendRequestWithJavaHttpClient(delay).thenApply(x -> </font><font>"webflux-java-http-client: "</font><font> + x);
    <b>return</b> Mono.fromFuture(stringCompletableFuture);
}
@GetMapping(value = </font><font>"/webflux-webclient"</font><font>)
<b>public</b> Mono<String> getUserUsingWebfluxWebclient(@RequestParam <b>long</b> delay) {
    <b>return</b> webClient.get().uri(</font><font>"/user/?delay={delay}"</font><font>, delay).retrieve().bodyToMono(String.<b>class</b>).map(x -> </font><font>"webflux-webclient: "</font><font> + x);
}
@GetMapping(value = </font><font>"/webflux-apache-client"</font><font>)
<b>public</b> Mono<String> apache(@RequestParam <b>long</b> delay) {
    <b>return</b> Mono.fromCompletionStage(sendRequestWithApacheHttpClient(delay).thenApply(x -> </font><font>"webflux-apache-client: "</font><font> + x));
}
</font>

User-Service(第三方服务)公开单个端点GET“/ user?delay = {delay}”。延迟(ms)参数用于延迟仿真。如果我们发送/ user?delay = 10,则响应时间将为10 ms +网络延迟(AWS内部最小);

这个用户服务是我们的第三方服务(用户服务),它非常快,可以处理超过4000个请求/秒

对于性能测试,我们将使用Jmeter。我们将测试100,200,400,800个并发请求的服务,延迟10,100,500毫秒。每个实施总共12个测试。

重要的提示:

我们仅针对热服务器测量性能:在每次测试之前,我们的服务处理了100万个请求(用于JIT编译器和JVM优化)

测试代码 https://github.com/Aleksandr-Filichkin/spring-mvc-vs-webflux

测试结果点击标题见原文

结论(在单核,1GB RAM服务器实例上):

Spring Webflux在所有测试情况下都获胜 ,包括使用WebClient和Apache clients情况! 

当底层服务很慢(500ms)时,有最显着的差异(比阻塞Servlet快4倍);它比使用CompetableFuture的非阻塞Servlet快15-20%;此外,与Servlet(20 vs 220)相比,它不会创建大量线程。

不幸的是,我们无法在任何地方使用WebFlux,因为我们需要异步驱动程序/客户端。否则,我们必须创建自定义线程池/包装器。

Servlet阻塞方式仅适用于底层服务快速(10ms)的情况。

Servlet非阻塞方式是一个非常好的解决方案,对于底层服务很慢(500毫秒)的情况。只有在有大量请求的情况下,它才会输给Webflux。

附注:

  • 对于单核,1GB RAM服务器实例,Java 11 Http Client比Apache Http客户端慢(性能降低约30%)
  • Spring WebClient与Apache Http Client(都使用netty)在单核,1GB RAM服务器实例上具有相同的性能
  • 当你只有一个核心和一个小内存时,WebFlux和 Java 11 Http Client的组合运行时模型不能很好地工作( https://github.com/spring-projects/spring-framework/issues/22333

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Programming Amazon Web Services

Programming Amazon Web Services

James Murty / O'Reilly Media / 2008-3-25 / USD 49.99

Building on the success of its storefront and fulfillment services, Amazon now allows businesses to "rent" computing power, data storage and bandwidth on its vast network platform. This book demonstra......一起来看看 《Programming Amazon Web Services》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具