《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 http://www.iocoder.cn/Performance-Testing/SpringMVC-Webflux-benchmark/ 「芋道源码」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注**微信公众号:【芋道源码】**有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

推荐阅读如下 SpringMVC 和 WebFlux 文章:

本文在提供完整代码示例,可见 https://github.com/YunaiV/SpringBoot-Labslab-06 目录。

原创不易,给点个 Star 嘿,一起冲鸭!

1. 概述

自 Spring 5.x 发布后,新增了 Spring WebFlux ,一个基于 Reactor 实现的响应式 Web 框架。其功能最大的特点,在于非阻塞异步,所以其需要运行如下环境下:

  • 1、支持 Servlet 3.1 的 Web 容器中,例如说 Tomcat、Jetty、Undertow 。
  • 2、Netty 网络通信框架,在 Webflux 使用的是 Reactor Netty 框架进行实现。并且,在使用 spring-boot-starter-webflux 时,默认使用的就是这种环境。

得益于 Webflux 框架,我们可以非常的将相对耗时的 IO 操作,提交到线程池中,从而达到异步非阻塞的功能,进一步提升并发性能。当然,实际上,我们使用 Servlet 3.1 + 线程池,也能实现非阻塞的效果,但是相比来说会麻烦一些。

艿艿:上面的这段话,可能写的有点绕口,或者不好理解,欢迎一起交流。

不过,Spring 在推出 WebFlux 后,带上响应式的概念,在目前这个时候,已经有些被魔化的带上“高并发”的说法?!所以,带着这样的好奇与疑惑,我们一起来做下 SpringMVC 和 Webflux 的性能基准测试。

在继续往下阅读本文之前,希望胖友已经阅读过如下两篇文章,因为有一些涉及到的关联知识,本文不会赘述:

2. 性能指标

《性能测试 —— Nginx 基准测试》 保持一致,我们还是以 QPS 作为性能的指标。

https://github.com/YunaiV/SpringBoot-Labs/tree/master/lab-06 中,我们提供了三个示例,分别是:

  • lab-06-springmvc-tomcat :Tomcat 9.0.16 NIO 模式 + Spring MVC

  • lab-06-webflux-tomcat :Tomcat 9.0.16 NIO 模式 + Webflux

    相比上个示例,保持使用的 Tomcat 的前提下,将 SpringMVC 替换成 Webflux 。

  • lab-06-webflux-netty :Netty 4.1.33.Final + Webflux

    相比上个示例,保持使用的 Weblufx 的前提下,将 Tomcat 替换成 Netty 。

胖友可以使用 mvn package 命令,打包出不同的示例,进行压力测试。

3. 测试环境

  • 型号 :ecs.c5.xlarge

    艿艿:和我一样抠门(穷)的胖友,可以买竞价类型服务器,使用完后,做成镜像。等下次需要使用的时候,恢复一下。HOHO 。

  • 系统 :CentOS 7.6 64位

  • CPU :4 核

  • 内存 :8 GB

  • 磁盘 :40 GB ESSD 云盘

  • JDK :openjdk version "1.8.0_212"

  • JVM 参数 :-Xms2g -Xmx2g -Xmn1g -XX:MaxMetaspaceSize=256m -Xss256k

因为我们在跑的过程,发现 wrk 占用 CPU 不是很高,所以直接本机运行。

有一点要注意,JVM 本身有预热的过程,Tomcat、Jetty、Undertow 本也有预热的过程(例如说,线程的初始化),所以需要多次测试,取平均值。

本文,我们使用 wrk 如下命令进行测试:

./wrk -t50 -c并发 -d30s http://127.0.0.1:8080

  • -t50 参数,设置 50 并发线程。
  • -c并发 参数,设置并发连接,目前会按照 300、1000、3000、5000 的维度,进行测试。
  • -d30s 参数,设置执行 30s 的时长的 HTTP 请求。
  • http://127.0.0.1:8080 参数,请求本地的 Web 服务。

下面,让我们进入正式的测试。在每一轮中,我们将测试相同场景下,SpringMVC 和 Webflux 的表现。

4. 第一轮

在这轮中,我们想先来测试下,在逻辑中完全无 IO 的情况下,三者的性能情况。请求的示例接口如下:

  • Spring MVC ,直接返回 "world" 字符串。

    @GetMapping("/hello")
    public String hello() {
    return "world";
    }

  • Spring Webflux ,直接返回 "world" 字符串的 Mono 对象。

    @GetMapping("/hello")
    public Mono<String> hello() {
    return Mono.just("world");
    }

4.1 SpringMVC + Tomcat

🚚 300 并发

$ ./wrk -t50 -c300 -d30s http://127.0.0.1:8080/hello

Running 30s test @ http://127.0.0.1:8080/hello
50 threads and 300 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 10.82ms 8.36ms 180.77ms 92.82%
Req/Sec 585.78 64.82 2.22k 77.93%
876355 requests in 30.10s, 98.78MB read
Requests/sec: 29115.47
Transfer/sec: 3.28MB

  • 29115 QPS
  • 10.82ms Avg Latency

🚚 1000 并发

$./wrk -t50 -c1000 -d30s http://127.0.0.1:8080/hello

Running 30s test @ http://127.0.0.1:8080/hello
50 threads and 1000 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 34.91ms 6.73ms 101.04ms 82.96%
Req/Sec 574.99 64.19 2.67k 83.73%
863098 requests in 30.10s, 97.27MB read
Requests/sec: 28678.48
Transfer/sec: 3.23MB

  • 28678 QPS
  • 34.91ms Avg Latency

🚚 3000 并发

Running 30s test @ http://127.0.0.1:8080/hello
50 threads and 3000 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 103.25ms 15.48ms 303.58ms 92.31%
Req/Sec 579.84 85.08 4.05k 88.02%
870323 requests in 30.10s, 98.05MB read
Requests/sec: 28911.19
Transfer/sec: 3.26MB

  • 28911 QPS
  • 103.25ms Avg Latency

🚚 5000 并发

Running 30s test @ http://127.0.0.1:8080/hello
50 threads and 5000 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 169.61ms 27.88ms 558.41ms 93.53%
Req/Sec 585.99 185.74 7.03k 80.49%
876680 requests in 30.10s, 98.75MB read
Requests/sec: 29126.46
Transfer/sec: 3.28MB

  • 29126 QPS
  • 169.61ms Avg Latency

小结

总的来说,QPS 比较稳定在 29000 左右,而延迟逐步提升。

4.2 Webflux + Tomcat

🚚 300 并发

$ ./wrk -t50 -c300 -d30s http://127.0.0.1:8080/hello

Running 30s test @ http://127.0.0.1:8080/hello
50 threads and 300 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 13.51ms 5.53ms 146.89ms 83.08%
Req/Sec 450.70 42.24 1.53k 77.86%
674480 requests in 30.09s, 76.02MB read
Requests/sec: 22413.32
Transfer/sec: 2.53MB

  • 22413 QPS
  • 13.51ms Avg Latency

🚚 1000 并发

$ ./wrk -t50 -c1000 -d30s http://127.0.0.1:8080/hello

Running 30s test @ http://127.0.0.1:8080/hello
50 threads and 1000 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 45.58ms 7.52ms 242.98ms 85.81%
Req/Sec 439.07 49.56 1.76k 85.46%
656334 requests in 30.10s, 73.97MB read
Requests/sec: 21805.86
Transfer/sec: 2.46MB

  • 21805.86 QPS
  • 45.58ms Avg Latency

🚚 3000 并发

$ ./wrk -t50 -c3000 -d30s http://127.0.0.1:8080/hello

Running 30s test @ http://127.0.0.1:8080/hello
50 threads and 3000 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 136.81ms 21.59ms 479.21ms 94.26%
Req/Sec 437.68 103.92 4.55k 82.03%
655949 requests in 30.10s, 73.92MB read
Requests/sec: 21791.89
Transfer/sec: 2.46MB

  • 21791.89 QPS
  • 136.81ms Avg Latency

🚚 5000 并发

$ ./wrk -t50 -c5000 -d30s http://127.0.0.1:8080/hello

Running 30s test @ http://127.0.0.1:8080/hello
50 threads and 5000 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 220.63ms 37.76ms 432.82ms 92.59%
Req/Sec 448.51 199.83 4.37k 74.60%
660039 requests in 30.10s, 74.37MB read
Requests/sec: 21925.27
Transfer/sec: 2.47MB

  • 21925.27 QPS
  • 220.63ms Avg Latency

小结

  • 从自身角度:总的来说,QPS 比较稳定在 21000 左右,而延迟逐步提升。
  • 对比 SpringMVC + Tomcat 角度:因为 Webflux 使用的是 Tomcat 对 Servlet 3.1 的实现,而这个场景的逻辑中,是完全没有 IO 的,所以“多余”了异步的过程,反倒带来了性能的下降。

4.3 Webflux + Netty

🚚 300 并发

$./wrk -t50 -c300 -d30s http://127.0.0.1:8080/hello

Running 30s test @ http://127.0.0.1:8080/hello
50 threads and 300 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 8.07ms 5.53ms 72.89ms 76.78%
Req/Sec 804.91 143.55 4.62k 78.17%
1203712 requests in 30.10s, 95.28MB read
Requests/sec: 39986.10
Transfer/sec: 3.17MB

  • 39986.10 QPS
  • 8.07ms Avg Latency

🚚 1000 并发

$./wrk -t50 -c1000 -d30s http://127.0.0.1:8080/hello

Running 30s test @ http://127.0.0.1:8080/hello
50 threads and 1000 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 22.00ms 7.60ms 261.83ms 84.69%
Req/Sec 0.90k 210.39 13.14k 89.39%
1342217 requests in 30.10s, 106.24MB read
Requests/sec: 44593.68
Transfer/sec: 3.53MB

  • 44593.68 QPS
  • 22.00ms Avg Latency

🚚 3000 并发

$./wrk -t50 -c3000 -d30s http://127.0.0.1:8080/hello

Running 30s test @ http://127.0.0.1:8080/hello
50 threads and 3000 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 72.83ms 24.03ms 1.02s 82.76%
Req/Sec 803.62 247.83 9.80k 87.97%
1208192 requests in 30.11s, 95.63MB read
Requests/sec: 40123.56
Transfer/sec: 3.18MB

  • 40123.56 QPS
  • 72.83ms Avg Latency

🚚 5000 并发

$./wrk -t50 -c5000 -d30s http://127.0.0.1:8080/hello

Running 30s test @ http://127.0.0.1:8080/hello
50 threads and 5000 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 126.80ms 68.82ms 1.98s 90.25%
Req/Sec 764.53 312.29 17.26k 84.27%
1141219 requests in 30.17s, 90.33MB read
Socket errors: connect 0, read 0, write 0, timeout 37
Requests/sec: 37823.63
Transfer/sec: 2.99MB

  • 37823.63 QPS
  • 126.80ms Avg Latency

小结

  • 从自身角度:随着并发请求的上升,QPS 先从 4w 左右,涨到 4.4w ,然后下滑到 3.8W 左右。

  • 对比 SpringMVC + Tomcat 角度:Netty 相比 Tomcat 的线程模型,更加简洁。主要体现在 Netty worker 线程解析好请求,直接进行了 Webflux 的逻辑执行,而 Tomcat Poller 线程拿到请求后,具体的请求需要丢给 Tomcat Worker 线程来解析,再进行了 SpringMVC 的逻辑执行。这样,就导致 Tomcat 多了一次线程的切换,而这个场景的逻辑中,是完全没有 IO 的,所以性能会相对低。

    不了解 Tomcat NIO 线程模型的胖友,可以看看如下两篇文章:

    不了解 Netty NIO 线程模型的胖友,可以看看如下文章:

    当然,胖友可以思考下,为什么 Tomcat 会比 Netty 多了一个线程池呢?其实是殊途同归的。然后,如果有使用过 Dubbo 的胖友,再去看看 Dubbo 的线程模型,在体会一波。

    🔥 其实,Tomcat 的 Worker 线程池,是为了并发执行业务逻辑,往往来说,业务逻辑都是有 IO 操作的,所以进行了这样的设计。而 Netty 的 Worker 线程,和 Tomcat 的 Poller 线程,是基本等价的。实际场景下,Netty Worker 线程,解码完消息包后,我们也会自己创建一个业务线程池,将解析的消息丢入其中,进行执行逻辑。 也就是说,本质上,是将具有 IO 操作的逻辑,丢到线程池中,避免有限的 Netty Worker 线程,或者 Tomcat Poller 线程被阻塞。 然后,我们把这个设计思路,带到 Webflux 上,是不是也是通的,嘻嘻。

  • 对比 Webflux + Tomcat 角度:相比来说,Netty 会比 Tomcat 更加适合作为 Webflux 的运行环境。

5. 第二轮

在这轮中,我们要来测试下,在逻辑中有 IO 的情况下,三者的性能情况。考虑到让测试更加简单,我们采用 Thread.sleep(100L) 暂停线程 100ms 的方式,来模拟 IO 阻塞的行为。请求的示例接口如下:

  • Spring MVC ,先 sleep 100ms ,再返回 "world" 字符串。

    @GetMapping("/sleep")
    public String sleep() throws InterruptedException {
    Thread.sleep(100L);
    return "world";
    }

  • Webflux ,先 sleep 100ms ,再返回 "world" 字符串的 Mono 对象。

    @GetMapping("/sleep")
    public Mono<String> sleep() {
    return Mono.defer(() -> {
    try {
    Thread.sleep(100L);
    } catch (InterruptedException ignored) {
    }
    return Mono.just("world");
    }).subscribeOn(Schedulers.parallel());
    }

    • 因为 sleep 会阻塞 100ms ,所以我们将逻辑的执行,通过 subscribeOn(Schedulers.parallel()) 代码块,调度到 Reactor 内置的用于并发执行的线程池,它的大小是 CPU 的线程数。例如说,本文使用的阿里云服务器,创建的线程池大小就是 4 。😈 为什么提这个呢?下文我们就会明白了。

5.1 SpringMVC + Tomcat

🚚 300 并发

$./wrk -t50 -c300 -d30s http://127.0.0.1:8080/sleep

Running 30s test @ http://127.0.0.1:8080/sleep
50 threads and 300 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 150.48ms 44.14ms 216.52ms 48.29%
Req/Sec 39.86 11.50 70.00 81.41%
59735 requests in 30.10s, 6.73MB read
Requests/sec: 1984.58
Transfer/sec: 228.96KB

  • 1984 QPS
  • 150.48ms Avg Latency

🚚 1000 并发

$./wrk -t50 -c1000 -d30s http://127.0.0.1:8080/sleep

Running 30s test @ http://127.0.0.1:8080/sleep
50 threads and 1000 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 497.76ms 118.19ms 955.99ms 89.35%
Req/Sec 40.54 19.53 190.00 68.86%
59815 requests in 30.10s, 6.73MB read
Requests/sec: 1987.28
Transfer/sec: 229.00KB

  • 1987 QPS
  • 497.76ms Avg Latency

🚚 3000 并发

$./wrk -t50 -c3000 -d30s http://127.0.0.1:8080/sleep

Running 30s test @ http://127.0.0.1:8080/sleep
50 threads and 3000 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 1.46s 286.08ms 1.65s 93.85%
Req/Sec 97.48 97.90 590.00 82.99%
59881 requests in 30.10s, 6.74MB read
Requests/sec: 1989.43
Transfer/sec: 229.25KB

  • 1989 QPS
  • 1.46s Avg Latency

🚚 5000 并发

$./wrk -t50 -c5000 -d30s http://127.0.0.1:8080/sleep

Running 30s test @ http://127.0.0.1:8080/sleep
50 threads and 5000 connections

Thread Stats Avg Stdev Max +/- Stdev
Latency 665.93ms 651.42ms 1.95s 76.81%
Req/Sec 110.86 154.20 0.97k 86.41%
59897 requests in 30.10s, 6.74MB read
Socket errors: connect 0, read 0, write 0, timeout 54494
Requests/sec: 1989.93
Transfer/sec: 229.31KB

  • 1989 QPS
  • 665.93ms Avg Latency

小结

  • 从自身角度:QPS 稳定在 2000 QPS 不到,这个是为什么呢?默认情况下,Spring Boot 设置内嵌的 Tomcat 的 Worker 线程池大小为 200 ,加上每个逻辑需要 sleep 100 ms ,所以每个线程能处理 10 个情况,所以 QPS 最大就在 10 * 200 = 2000 ,所以测试结果就基本在 2000 QPS 。

5.2 Webflux + Tomcat

🚚 300 并发

$./wrk -t50 -c300 -d30s http://127.0.0.1:8080/sleep

Running 30s test @ http://127.0.0.1:8080/sleep
50 threads and 300 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 0.00us 0.00us 0.00us -nan%
Req/Sec 2.96 3.61 20.00 83.63%
1065 requests in 30.06s, 122.72KB read
Socket errors: connect 0, read 0, write 0, timeout 1065
Requests/sec: 35.43
Transfer/sec: 4.08KB

  • 35.43 QPS
  • 0.00us Avg Latency 【计算错误】

是不是看到这样的性能,一脸懵逼?淡定,我们下面会解释。

🚚 1000 并发

$./wrk -t50 -c1000 -d30s http://127.0.0.1:8080/sleep

Running 30s test @ http://127.0.0.1:8080/sleep
50 threads and 1000 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 1.01s 552.42ms 1.91s 57.89%
Req/Sec 7.56 7.71 50.00 85.68%
1192 requests in 30.10s, 137.36KB read
Socket errors: connect 0, read 0, write 0, timeout 1116
Requests/sec: 39.61
Transfer/sec: 4.56KB

  • 35.43 QPS
  • 0.00us Avg Latency 【计算错误】

🚚 3000 并发

$./wrk -t50 -c3000 -d30s http://127.0.0.1:8080/sleep

Running 30s test @ http://127.0.0.1:8080/sleep
50 threads and 3000 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 983.66ms 543.02ms 1.96s 60.00%
Req/Sec 11.37 9.50 40.00 76.67%
1184 requests in 30.10s, 136.44KB read
Socket errors: connect 0, read 0, write 0, timeout 1124
Requests/sec: 39.34
Transfer/sec: 4.53KB

  • 39.34 QPS
  • 983.66ms Avg Latency

🚚 5000 并发

$./wrk -t50 -c5000 -d30s http://127.0.0.1:8080/sleep

Running 30s test @ http://127.0.0.1:8080/sleep
50 threads and 5000 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 1.25s 480.74ms 1.94s 65.45%
Req/Sec 12.07 10.54 40.00 73.63%
1187 requests in 30.10s, 136.78KB read
Socket errors: connect 0, read 0, write 0, timeout 1132
Requests/sec: 39.44
Transfer/sec: 4.54KB

  • 39.44 QPS
  • 1.25s Avg Latency

小结

  • 从自身角度:QPS 稳定在 40 QPS 不到,这个是为什么呢?在上文中,我们也提到,Reactor 内置的 parallel 线程,线程池大小是 4 ,因为逻辑里 sleep 了 100ms ,所以每个线程每秒能处理 10 个请求,这个就导致最大 QPS 最大就在 10 * 4 = 40 。

5.3 Webflux + Netty

Webflux + Netty 的组合,效果和 Webflux + Tomcat 是一致的,就不重复测试了。

6. 第三轮

看到此处,可能有胖友就懵逼了,“哎呀,Webflux 什么情况,性能这么差,是不是你测试错了呀?”。我们来使用 Reactor elastic 调度器,看看性能情况。示例代码如下:

@GetMapping("/sleep2")
public Mono<String> sleep2() {
return Mono.defer(() -> {
try {
Thread.sleep(100L);
} catch (InterruptedException ignored) {
}
return Mono.just("world");
}).subscribeOn(Schedulers.elastic());
}

  • 怎么理解 Reactor 内置的 elastic 调度器呢?胖友可以先简单理解成 ExecutorService 的 newCachedThreadPool 线程池,一个无限大的线程池。我们来想想下,如果使用了它,那么每个请求会进入线程池中,进行 sleep 100ms ,然后完成返回,并且不限量,这个非常关键!

下面,开始我们的表演。这里,我们依然使用 Webflux + Tomcat 的组合。

🚚 300 并发

$wrk -t50 -c300 -d30s http://127.0.0.1:8080/sleep2

Running 30s test @ http://127.0.0.1:8080/sleep2
50 threads and 300 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 100.72ms 1.94ms 300.90ms 97.04%
Req/Sec 59.47 3.25 102.00 95.77%
89122 requests in 30.10s, 10.04MB read
Requests/sec: 2961.05
Transfer/sec: 341.58KB

  • 2961.05 QPS
  • 100.72ms Avg Latency

🚚 1000 并发

$wrk -t50 -c1000 -d30s http://127.0.0.1:8080/sleep2

Running 30s test @ http://127.0.0.1:8080/sleep2
50 threads and 1000 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 104.23ms 14.70ms 353.66ms 95.03%
Req/Sec 193.77 20.42 252.00 91.85%
288903 requests in 30.10s, 32.55MB read
Requests/sec: 9598.51
Transfer/sec: 1.08MB

  • 9598.51 QPS
  • 104.23ms Avg Latency

🚚 3000 并发

$wrk -t50 -c3000 -d30s http://127.0.0.1:8080/sleep2

./wrk -t50 -c3000 -d30s http://127.0.0.1:8080/sleep2
Running 30s test @ http://127.0.0.1:8080/sleep2
50 threads and 3000 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 185.33ms 103.05ms 1.30s 89.87%
Req/Sec 345.33 160.48 669.00 61.51%
468276 requests in 30.10s, 52.75MB read
Requests/sec: 15555.70
Transfer/sec: 1.75MB

  • 15555.70 QPS
  • 185.33ms Avg Latency

🚚 5000 并发

$wrk -t50 -c5000 -d30s http://127.0.0.1:8080/sleep2

[root@iZuf6hci646px19gg3hpuwZ wrk]# ./wrk -t50 -c5000 -d30s http://127.0.0.1:8080/sleep2
Running 30s test @ http://127.0.0.1:8080/sleep2
50 threads and 5000 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 269.61ms 157.51ms 1.90s 86.07%
Req/Sec 349.51 232.46 1.01k 62.82%
455185 requests in 30.10s, 51.28MB read
Requests/sec: 15122.01
Transfer/sec: 1.70MB

  • 15122.01 QPS
  • 269.61ms Avg Latency

小结

  • 从自身角度:
    • 300 和 1000 并发的时候,并发和 QPS 基本是 1:10 ,这是因为每个请求 sleep 100ms ,那么在线程池足够大小的时候,最大 QPS 是并发 * 10 ,所以 300 并发时,是 3000 QPS 不到,1000 并发时,是 10000 QPS 不到。
    • 3000 和 5000 并发的时候,并发量在 15000 QPS 左右,趋于稳定。不过按照上面并发和 QPS 是 1:10 的说法,应该要能达到 30000 或 50000 QPS ,但是从实际自己打的日志,到不了这么多线程数。再具体的原因,就暂时没去分析。
  • 对比 SpringMVC + Tomcat 角度:Webflux 比 SpringMVC 的 QPS 提升很多。我们从示例中,也可以看到,得益于可以将请求提交到 Reactor 无限大的线程池中,从而提高并发性能。不过,我们也一定要清楚这一点,为什么 Webflux 性能得到了提升。当然,我们也可以使用 SpringMVC + Servlet 3.1 的特性,也能实现类似的效果,只是说会麻烦一些。

666. 彩蛋

舒服,通过这个测试,对 Webflux 的一些东西,也想的更明白了。当然,因为本文并未模拟真正接入有 IO 的情况下的测试,这个我们留在网关 Spring Cloud Gateway 与 Zuul 的对比中。

另外,Webflux 如果搭配上协程 ,会不会更加愉快呢?毕竟,Java 的线程,是重量级的,最小需要分配大内存,也是 256K ,并且切换线上下文,也是不低的成本。

推荐以及参考如下文章:

文章目录
  1. 1. 1. 概述
  2. 2. 2. 性能指标
  3. 3. 3. 测试环境
  4. 4. 4. 第一轮
    1. 4.1. 4.1 SpringMVC + Tomcat
    2. 4.2. 4.2 Webflux + Tomcat
    3. 4.3. 4.3 Webflux + Netty
  5. 5. 5. 第二轮
    1. 5.1. 5.1 SpringMVC + Tomcat
    2. 5.2. 5.2 Webflux + Tomcat
    3. 5.3. 5.3 Webflux + Netty
  6. 6. 6. 第三轮
  7. 7. 666. 彩蛋