本系列其他文章见:《响应式Spring的道法术器》。
前情提要:Reactor快速上手 | Spring WebFlux快速上手
本文源码
前面总是“安利”异步非阻塞的好处,下面我们就实实在在感受一下响应式编程在高并发环境下的性能提升。异步非阻塞的优势体现在I/O操作方面,无论是文件I/O、网络I/O,还是数据库读写,都可能存在阻塞的情况。
我们的测试内容有三:
RestTemplate
和非阻塞的WebClient
;说明:本节进行的并非是严谨的基于性能调优的需求的,针对具体业务场景的负载测试。本节测试场景简单而直接,各位朋友GET到我的点即可。
此外:由于本节主要是进行横向对比测试,因此不需要特定的硬件资源配置,不过还是建议在Linux环境下进行测试,我最初是在Win10上跑的,当用户数上来之后出现了不少请求失败的情况,下边的测试数据是在一台系统为Deepin Linux(Debian系)的笔记本上跑出来的。
那么我们就开始搭建这套简单粗暴的测试环境吧~
1)搭建待测试项目
我们分别基于WebMVC和WebFlux创建两个项目:mvc-with-latency
和WebFlux-with-latency
。
为了模拟阻塞,我们分别在两个项目中各创建一个带有延迟的/hello/{latency}
的API。比如/hello/100
的响应会延迟100ms。
mvc-with-latency
中创建HelloController.java
:
@RestController
public class HelloController {
@GetMapping("/hello/{latency}")
public String hello(@PathVariable long latency) {
try {
TimeUnit.MILLISECONDS.sleep(latency); // 1
} catch (InterruptedException e) {
return "Error during thread sleep";
}
return "Welcome to reactive world ~";
}
}
WebFlux-with-latency
中创建HelloController.java
:
@RestController
public class HelloController {
@GetMapping("/hello/{latency}")
public Mono hello(@PathVariable int latency) {
return Mono.just("Welcome to reactive world ~")
.delayElement(Duration.ofMillis(latency)); // 1
}
}
delayElement
操作符来实现延迟。然后各自在application.properties
中配置端口号8091和8092:
server.port=8091
启动应用。
2)编写负载测试脚本
本节我们采用gatling来进行测试。创建测试项目gatling-scripts
。
POM中添加gatling依赖和插件(目前gradle暂时还没有这个插件,所以只能是maven项目):
<dependencies>
<dependency>
<groupId>io.gatling.highchartsgroupId>
<artifactId>gatling-charts-highchartsartifactId>
<version>2.3.0version>
<scope>testscope>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>io.gatlinggroupId>
<artifactId>gatling-maven-pluginartifactId>
<version>2.2.4version>
plugin>
plugins>
build>
在src/test
下创建测试类,gatling使用scala语言编写测试类:
import io.gatling.core.scenario.Simulation
import io.gatling.core.Predef._
import io.gatling.http.Predef._
import scala.concurrent.duration._
class LoadSimulation extends Simulation {
// 从系统变量读取 baseUrl、path和模拟的用户数
val baseUrl = System.getProperty("base.url")
val testPath = System.getProperty("test.path")
val sim_users = System.getProperty("sim.users").toInt
val httpConf = http.baseURL(baseUrl)
// 定义模拟的请求,重复30次
val helloRequest = repeat(30) {
// 自定义测试名称
exec(http("hello-with-latency")
// 执行get请求
.get(testPath))
// 模拟用户思考时间,随机1~2秒钟
.pause(1 second, 2 seconds)
}
// 定义模拟的场景
val scn = scenario("hello")
// 该场景执行上边定义的请求
.exec(helloRequest)
// 配置并发用户的数量在30秒内均匀提高至sim_users指定的数量
setUp(scn.inject(rampUsers(sim_users).over(30 seconds)).protocols(httpConf))
}
如上,这个测试的场景是:
其中URL和用户量通过base.url
、test.path
、sim.users
变量传入,借助maven插件,通过如下命令启动测试:
mvn gatling:test -Dgatling.simulationClass=test.load.sims.LoadSimulation -Dbase.url=http://localhost:8091/ -Dtest.path=hello/100 -Dsim.users=300
就表示用户量为300的对http://localhost:8091/hello/100
的测试。
3)观察线程数量
测试之前,我们打开jconsole观察应用(连接MVCWithLatencyApplication)的线程变化情况:
如图(分辨率问题显示不太好)是刚启动无任何请求进来的时候,默认执行线程有10个,总的线程数31-33个。
比如,当进行用户数为2500个的测试时,执行线程增加到了200个,总的线程数峰值为223个,就是增加的这190个执行线程。如下:
由于在负载过去之后,执行线程数量会随机减少回10个,因此看最大线程编号估算线程个数的话并不靠谱,我们可以用“峰值线程数-23”得到测试过程中的执行线程个数。
4)负载测试
首先我们测试mvc-with-latency
:
测试数据如下(Tomcat最大线程数200,延迟100ms):
由以上数据可知:
这里我们不难得出原因,那就是当所有可用线程都在阻塞状态的话,后续再进入的请求只能排队,从而当达到最大线程数之后,响应时长开始上升。我们以6000用户的报告为例:
这幅图是请求响应时长随时间变化的图,可以看到大致可以分为五个段:
所有请求的响应时长分布如下图所示:
A/E段与C段的时长只差就是平均的排队等待时间。在持续的高并发情况下,大部分请求是处在C段的。而且等待时长随请求量的提高而线性增长。
增加Servlet容器处理请求的线程数量可以缓解这一问题,就像上边把最大线程数量从默认的200增加的400。
最高200的线程数是Tomcat的默认设置,我们将其设置为400再次测试。在application.properties
中增加:
server.tomcat.max-threads=400
测试数据如下:
由于工作线程数扩大一倍,因此请求排队的情况缓解一半,具体可以对比一下数据:
这也再次印证了我们上边的分析。增加线程数确实可以一定程度下提高吞吐量,降低因阻塞造成的响应延时,但此时我们需要权衡一些因素:
我们再来看一下对于WebFlux-with-latency
的测试数据:
reactor-http-nio-X
和parallel-X
的线程,我这是四核八线程的i7,所以X
从1-8),因为异步非阻塞条件下,程序逻辑是由事件驱动的,并不需要多线程并发;可见,非阻塞的处理方式规避了线程排队等待的情况,从而可以用少量而固定的线程处理应对大量请求的处理。
除此之外,我又一步到位直接测试了一下20000用户的情况:
1. 对mvc-with-latency
的测试由于出现了许多的请求fail而以失败告终;
2. 而WebFlux-with-latency
应对20000用户已然面不改色心不慌,吞吐量达到7228 req/sec(我擦,正好是10000用户下的两倍,太巧了今天怎么了,绝对是真实数据!),95%响应时长仅117ms。
最后,再给出两个吞吐量和响应时长的图,更加直观地感受异步非阻塞的WebFlux是如何一骑绝尘的吧:
此时,我们更加理解了Nodejs的骄傲,不过我们大Java语言也有了Vert.x和现在的Spring WebFlux。
本节我们进行服务器端的性能测试,下一节继续分析Spring WebFlux的客户端工具WebClient的性能表现,它会对微服务架构的系统带来不小的性能提升呢!