转载

SpringBoot性能比较:Spring MVC与WebFlux

在这里我想谈谈曾经在项目中遇到的有趣的事情。我们为我们的客户在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 )
原文  https://www.jdon.com/51720
正文到此结束
Loading...