最近Josh Long等Spring社区的大师来中国进行交流与宣传,并现场建立了一个spring-webflux的服务demo,首先从start.spring.io上构建一个基于spring boot、reactive、mongo的reservation微服务,然后基于微服务构建routing支持,引入Spring Cloud Gateway,Spring Hystrix,Spring Security,Flow Control,Client检测,Rsocket等机制。整体Demo演示下来,让与会的开发者充分了解Spring Framework的新功能与机制。
webflux是基于netty server,其实很早以前我就有一个疑惑,既然tomcat是阻塞式服务器,而netty是一个优秀的nio通信框架,为什么没有一个基于netty的server呢,性能会不会优秀特别多呢。后来研究spring boot源码的时候,发现它内置了4个server,分别是jetty、netty、tomcat和undertow,路径位于org.springframework.boot.web.embedded包下。因此其实netty server已经有了,只是我之前并不知道罢了,既然有了netty和tomcat,很自然的我会希望对他们进行一次benchmark测试下是否存在性能差异。
我先搜了下看有没有前人的测试结果,国内还是有的,一篇叫Spring WebFlux性能测试——响应式Spring的道法术器的博客已经写了一篇比较系统的关于webflux与webmvc的性能测试总结。
这是他的测试结果,说实话,看到这个结果我还是挺震撼的,异步事件模型的网络性能优势竟然如此巨大,那看来在对高并发有需求的场景下我们应该选用webflux和netty server进行编程了。
于是我激动的开启了自己的benchmark之旅,不过却并没能如同这位博主一样比较顺利。
Apache ab.exe测试
我没有用过gatling,因此我首先还是用最简单的工具apache.exe进行测试
首先是对web-mvc的测试,测试一个 /hello的接口
@GetMapping("/hello")
public String getEcho() throws InterruptedException {
Thread.sleep (100);
return "ok";
}
测试命令: ab -n 20000 -c 5000 http://localhost:8082/hello
测试结果:
可以看到95%的响应在3s内完成,说明在达到spring boot默认线程数上限后,剩余的请求就会开始排队了,spring boot默认线程数是200,可以通过server.tomcat.max-threads属性进行修改,这里我改成了400,在测试过程中我用jconsole进行观测,发现idea的线程数峰值达到了425,和预想的情况相符。
下面是对webflux进行测试
webflux接口代码:
@GetMapping("/hello")
public Mono<String> hello() {
return Mono.just ("hello").delayElement (Duration.ofMillis (100));
}
测试结果如下:
我很惊讶,因为和预期的效果差距很远,可以看到吞吐量和95% 请求响应时间上限仅仅比 webmvc的版本优秀一点点(1606->1548,2944->3050)。按照之前博主的结果,95%结果应该在110ms以内,我又调整了参数,分别对5000并发-20000并发进行了测试,结果webflux在一些并发数情况下,性能要比webmvc还差。于是我猜想是不是我的测试工具有问题,毕竟ab.exe测试的结论过于简单。于是我开始使用jmeter
Jmeter测试
我用jmeter对webflux进行了测试,设置如下
其中并发数5000,在2s内到达5000,每个并发请求15次,结果大吃一惊,webflux不但吞吐量随着并发的增加而不断下降,在后期并发打满的情况下,httpresponse还出现了error。
95%响应时间为3438ms,也不如人意。
然后我对webmvc进行了测试
结果如下
依然有许多error,95%响应时间也是3930ms,并不令人满意。
后面我又增加了从3000-20000的并发数测试,结果都不令人满意,频繁出现error,而且响应时间也不理想。
到这里为止,我开始有点怀疑博主的测试结果了,不过毕竟我们用的测试工具不同,前面文章的博主用的gatling,而我是jmeter,于是我开始了我第三轮测试。
gatling
我借用了博主的测试脚本,地址是https://github.com/get-set/get-reactive/tree/master/gatling。 然后下载了gatling、scala、sbt环境。不过运行的时候始终报错,我研究了下gatling的官方demo,我猜测可能是gatling新版本的api产生了变化,博主用的 over 在新版本里面已经换成了 during。
修改了代码后,脚本可以正常运行,因为加入了1s的每个并发每次的请求间隔,所以我感觉这个更符合真实的并发场景,因此对它的结果更加产生了期待。
然后事实依然是残酷的,gatling的测试结果竟然仅仅比jmeter的要稍好一点,我猜测这个结果还是因为在请求间加入了时延导致的。我不断修改参数,对3000-10000的并发的两种server分别测试了20多次,依然没有得到一个理想的结果,下面贴一个不是很好的结果做个示例。
可以看到高并发下,95%请求已经达到了可怕的20s左右,并且有大量的error response。
不过中间有一次测试结果,我使用gatling测试5000并发,结果是95%结果为104ms,我非常惊讶,感觉又有了希望。不过再也没有重现过,后来我检查了下代码,我将请求repeat次数改小了,而during依然是30s,因此我猜测可能是因为实际运行过程中,线程并没有完全打满5000导致的。
测试环境的问题?
我后面回过头去看前面博主的博客,发现评论区很多小伙伴出现了和我一样的问题,就是jmerer测试webflux和webmvc的性能差距并不太大,博主也进行了一些解答,其中提到了操作系统,而我测试确实用的是windows系统,而我们知道windows的nio采用的是IOCP而linux采用的是epoll,会不会是这个区别导致的呢?
于是我将程序部署到linux系统上,一开始我选择的是我的阿里云,后来做benchmark结果简直不忍直视,果然在高并发情况下,网络情况也是会影响结果的。我只好用本机的虚拟机进行测试,结果一波十几折,首先是因为虚拟机好久没用了,网络都忘了配置了,结果虚拟机和主机之间都无法通信,主机可以ping通虚拟机,反向则不行。捣鼓了半天后终于ok了,我开始测试,一开始测试3000并发,结果狂报错,io.netty.channel.unix.Errors$NativeIoException: accept(..) failed: Too many open files。一看是fd打开太多,我输入ulimit -n,果然虚拟机配置的默认上限只有1024, vi /etc/security/limits.conf,修改连接数为65535并重启,ulimt -n 确认连接数后测试没有继续报错。but but but no use啊...
测试结果依然没有好转:
测试的参数是5000并发,during是3s,每个并发重复10次。
可以看到无论是吞吐量还是95%响应时间,都terrible。看来webflux还有很多我需要深究的地方,要搞清楚他异步高性能的配置还需要下更大的功夫。
总结&TODO
经过前面的测试,可以知道在我当前的环境下,webflux并不是解决高性能高并发场景的银弹。
但是既然前面的博主有这样的测试结论,我相信他的数据结果也不是凭空来的,那么这接下来就是研究我们之间的环境究竟还有哪些差异。我可以想见的有:
- spring boot版本
- jdk版本
- 我是测试机与server都在同一台机器,测试中也发现CPU完全被打满了,而且测试脚本占用了大量CPU
- 操作系统
因此接下来需要在这些方面找找原因。
另外,我理解的webflux,确实非常适合做不间断的响应式数据推送的场景,例如滚动聊天室,百度贴吧最上面的那种聊天,交易信息推送等等。不过这些都是后话,也许以后我会把之前的一些响应式编程的总结记录一下,不过当前还是思考下benchmark的问题吧。