前言说明:通过zuul访问后端服务时,这个流程是如何的?当你用500线程并发访问zuul和用100线程并发访问zuul,zuul分别会用多少个线程去并发访问后端的服务?后端最多能承受多少个并发线程?zuul默认是Hystrix的信号量隔离,这个值对zuul并发访问后端时有什么影响?
可以通过这一篇来了解一下。
内容
作为微服务架构系统的入口,毫无疑问,Zuul的并发性能直接决定了整个系统的并发性能。本文结合前几篇文章的内容,在云服务器中部署了包含Eureka Server,Zuul等组件的1.0版本的微服务架构,并进行单点部署Zuul的压力测试,对其并发性能一探究竟。
版本
JVM监测工具:JVisualVM
压力测试工具:Apache Bench 2.3
JDK:1.8.0_171
SpringBoot:1.5.9.RELEASE
SpringCloud:Dalston.SR1
环境
处理器具体型号为Intel xeon(skylake) platinum 8163,主频2.5GHz。
说明
转载请说明出处:SpringCloud从入门到进阶(八)——单点部署Zuul的压力测试与调优(一)
步骤
微服务架构中,所有的请求都需要经过Zuul的转发才能到达具体的微服务。因此Zuul的并发量和可用性将直接影响甚至决定整个系统的并发量和可用性。在本篇文章中,我们使用压力测试工具Apache Bench,在局域网范围内搭建环境对特定接口进行压力测试,因此本示例只是考察CPU和内存对Zuul和微服务并发能力的影响,网络带宽、缓存、数据库、磁盘IO等因素不在本实例的讨论范围内,测试系统的吞吐量、服务端请求平均处理时间、用户请求平均等待时间等参数。
Apache Bench的安装、使用请参考Linux入门实践笔记(六)——压力测试工具Apache Bench的安装、使用和结果解读。下面,我们再重温下HTTP服务器性能指标:
吞吐量
吞吐量(Requests per second)是在某个并发度下服务器每秒处理的请求数。它是服务器并发处理能力的量化描述,单位是reqs/s。计算公式为:总请求数/处理请求的总耗时。 吞吐量越大说明服务器的性能越好。
请求平均处理时间
请求平均处理时间(Time per request,across all concurrent requests)是服务器处理请求的平均时间,计算公式为:处理请求的总耗时/总请求数。它是服务器吞吐量的倒数。也等于,用户请求平均等待时间/并发用户数。
请求平均等待时间
请求平均等待时间(Time per request)是用户等待请求响应的平均时间,计算公式为:处理请求的总耗时/(总请求数/并发用户数)。
“请求平均处理时间”和“请求平均等待时间”两个概念非常容易混淆,举一个例子进行说明:比如100个用户同时执行上传文档的操作,那么并发用户数为100,假设服务器可以同时处理这100个请求,并且每个文件上传操作的耗时都是1s。那么请求总耗时时间为1s,吞吐量为100reqs/s。请求平均处理时间为0.01s,请求平均等待时间为1s。也就是说,请求平均处理时间是从服务器的角度出发的,请求平均等待时间是从用户的角度出发的。
测试环境搭建
启动路由Zuul
执行下面的指令部署路由Zuul,将jvm的栈空间设置为512MB,并在本地的7199端口开启jmx监控,用来检测jvm的运行情况。由SpringCloud从入门到进阶(五)——路由接入Zuul及其单点部署可知,Zuul的路由规则为:"/routea/..." 匹配到微服务"application-serviceA"。
[user@ServerA7 jars]$ java -Dspring.profiles.active=peer2
-Dcom.sun.management.jmxremote.port=7199 #供JMX客户端远程连接用的端口号
-Dcom.sun.management.jmxremote.ssl=false #关闭账号密码认证,不安全,仅在开发阶段使用
-Dcom.sun.management.jmxremote.authenticate=false #关闭SSL
-Djava.rmi.server.hostname=106.117.142.x #指定本机供远程访问的IP地址,此处是本机的公网IP
-Xms512m -Xmx512m -jar zuul-1.0-SNAPSHOT.jar &
如果按照上述参数配置,仍然无法远程访问JVM,可以参考Linux入门实践笔记(七)——云服务器中配置Java项目的JMX连接失败问题解决记录。
启动微服务实例
此处在SpringCloud从入门到进阶(六)——使用SpringBoot搭建微服务的基础之上,在DemoController中增加测试接口"/timeconsume/{length}",使用sayHello方法和timeConsuming方法分别模拟简单操作和耗时操作(由代码可知,接口处理与网络带宽、缓存、数据库、磁盘IO无关)。
@PostMapping("/hello/{name}")
public String sayHello(@PathVariable(value = "name") String name,
@RequestParam(value = "from") String user){
String content="Hello "+name+",this is DemoTest.From "+user+"@:"+instanceID+".";
logger.log(Level.INFO,content);
return content;
}
@GetMapping("/timeconsume/{length}")
public String timeConsuming(@PathVariable(value = "length") int length){
try {
Thread.sleep(length);
}catch (Exception e){
e.printStackTrace();
}
String content="Successfully sleep "+length+" ms.";
logger.log(Level.INFO,content);
return content;
}
参照下面指令启动ServiceA的实例,同样在本地的7199端口开启jmx监控,将jvm的栈空间设置为起始512MB,最大1024MB。
[user@ServerA2 jars]$ java -Dspring.profiles.active=peer1 --Dcom.sun.management.jmxremote.port=7199 #供JMX客户端远程连接用的端口号
-Dcom.sun.management.jmxremote.ssl=false #关闭账号密码认证,不安全,仅在开发阶段使用
-Dcom.sun.management.jmxremote.authenticate=false #关闭SSL
-Djava.rmi.server.hostname=106.117.142.x #指定本机供远程访问的IP地址,此处是本机的公网IP
-Xms512m -Xmx1024m -jar service-1.0-SNAPSHOT.jar &
开始测试
在测试路径下创建ab的post测试所需要的参数文件params,内容为:
[user@ServerA6 ab]$ cat params from=lee
测试一:50个并发用户,执行50000次请求
1.1.1直接调用sayHello接口
直接调用sayHello接口,是为了记录该接口的处理速度,用于与通过Zuul调用做比较,以评估Zuul转发对请求延迟的影响。系统吞吐量在5600(请求/秒)左右,请求平均处理时间为0.177ms,请求平均等待时间为8.869ms,50000次请求都执行成功。
注:多测试几次,吞吐量会随着虚拟机指令的优化逐步稳定在5600左右。
[user@ServerA6 ab]$ ab -n 50000 -c 50 -p params -T application/x-www-form-urlencoded
http://172.26.125.115:8881/test/hello/leo
Time taken for tests: 8.869 seconds
#50000次请求都执行成功
Complete requests: 50000
Failed requests: 0
#系统吞吐量在5600(请求/秒)左右
Requests per second: 5637.54 [#/sec] (mean)
#请求平均等待时间为8.869ms
Time per request: 8.869 [ms] (mean)
#请求平均处理时间为0.177ms
Time per request: 0.177 [ms] (mean, across all concurrent requests)
Service资源使用情况:
压测过程中,CPU使用率在80%,堆内存的使用最大为220MB(堆空间为512MB),实时线程从44增加到85。此时CPU成了系统吞吐量进一步提升的瓶颈,此时的系统吞吐量可以视为单台2核服务器能承受的最大吞吐量,即5600左右(结论一)。
1.1.2通过路由Zuul调用sayHello接口(不做负载均衡)
系统吞吐量在4000(请求/秒)左右,请求平均处理时间为0.246ms,请求平均等待时间为12.297ms,50000次请求都执行成功。跟1.1.1的测试比较,可知,Zuul转发后,平均每个请求的等待时间增加了3.428ms。
[user@ServerA6 ab]$ ab -n 50000 -c 50 -p params -T
application/x-www-form-urlencoded http://172.26.125.117:7082/v1/routea/test/hello/leo
Time taken for tests: 12.297 seconds
Complete requests: 50000
Failed requests: 0
Requests per second: 4066.11 [#/sec] (mean)
Time per request: 12.297 [ms] (mean)
Time per request: 0.246 [ms] (mean, across all concurrent requests)
Zuul资源使用情况:
压测过程中,Zuul服务器的CPU使用率为100%,堆内存的使用最大为470MB(堆空间为512MB),实时线程从80增加到120。可见CPU为系统的瓶颈。
Service资源使用情况
压测过程中,Service服务器的CPU使用率为50%,堆内存的使用最大为270MB(堆空间为512MB),实时线程从48增加到75。可见由于Zuul请求转发的不及时,Service端的CPU和内存都有富余,Zuul成为微服务架构的瓶颈(结论二)。通过线程数量的变化可知,Zuul端虽然有50个线程转发用户请求,但是在Service端,只有大概40个线程处理请求。Zuul端转发请求的线程数与Service端处理请求的线程数之间是什么关系呢?(问题一)这里先暂且保留这个问题,在后续的文章中再具体解释。
1.2.1直接调用timeConsuming(200ms)接口
系统吞吐量在250(请求/秒)左右,请求平均处理时间为4.032ms,请求平均等待时间为201.622ms,50000次请求都执行成功。
[user@ServerA6 ab]$ ab -n 50000 -c 50 http://172.26.125.115:8881/test/timeconsume/200
Time taken for tests: 201.622 seconds
Complete requests: 50000
Failed requests: 0
Requests per second: 247.99 [#/sec] (mean)
Time per request: 201.622 [ms] (mean)
Time per request: 4.032 [ms] (mean, across all concurrent requests)
Service资源使用情况
压测过程中,Service服务器的CPU使用率起初为60%,随着响应的处理,逐步稳定到10%以内,堆内存的使用最大为270MB,实时线程从48增加到90。可见Service端的CPU和内存都有富余。
1.2.2通过Zuul调用timeConsuming接口
系统吞吐量在250(请求/秒)左右,请求平均处理时间为4.064ms,请求平均等待时间为203.210ms,50000次请求都执行成功。跟1.2.1的测试比较,可知,Zuul转发后,平均每个请求的等待时间增加了1.588ms。跟1.1.2的测试比较可知,Zuul在CPU资源从紧张到富余时,转发后请求的等待时间延迟从3.428ms降到了1.588ms。可见当Zuul的CPU高负荷运转时,其转发请求所带来的延迟就越高(结论三)。
[user@ServerA6 ab]$ ab -n 50000 -c 50 http://172.26.125.117:7082/v1/routea/test/timeconsume/200
Time taken for tests: 203.210 seconds
Complete requests: 50000
Failed requests: 0
Requests per second: 246.05 [#/sec] (mean)
Time per request: 203.210 [ms] (mean)
Time per request: 4.064 [ms] (mean, across all concurrent requests)
Zuul资源使用情况
压测过程中,Zuul服务器的CPU使用率在10%左右,堆内存的使用最大为470MB(堆空间为512MB),实时线程从79增加到120。此时Zuul端的CPU有富余。
Service资源使用情况
压测过程中,Service服务器的CPU使用率在7%左右,堆内存的使用最大为270MB(堆空间为512MB),实时线程从48增加到89。可见Service端的CPU和内存都有富余,可以承受更大的并发量。
测试二:200个并发用户,执行50000次请求
总请求次数不变,将并发用户数增大到200,来探究并发用户数增加与系统吞吐量的关系。
2.1.1直接调用sayHello接口
系统吞吐量在5500(请求/秒)左右,请求平均处理时间为0.183ms,请求平均等待时间为36.592ms,50000次请求都执行成功。跟1.1.1的测试比较,由于受CPU瓶颈影响,在并发用户数增大4倍之后,系统的吞吐量并没有增大,反而由于并发线程增多,堆内存的开销变大,系统吞吐量有略微的减少,并且用户等待时间从8.869增大到36.592ms,增大了4倍多(结论四)。
[user@ServerA6 ab]$ ab -n 50000 -c 200 -p params -T application/x-www-form-urlencoded
http://172.26.125.115:8881/test/hello/leo
Time taken for tests: 9.148 seconds
Complete requests: 50000
Failed requests: 0
Requests per second: 5465.68 [#/sec] (mean)
Time per request: 36.592 [ms] (mean)
Time per request: 0.183 [ms] (mean, across all concurrent requests)
Service资源使用情况
压测过程中,CPU使用率达到90%,堆内存的使用最大为410MB(堆空间为512MB),实时线程从48增加到238。
2.1.2通过Zuul调用sayHello接口
系统吞吐量在4200(请求/秒)左右,请求平均处理时间为0.237ms,请求平均等待时间为47.428ms,50000次请求中有24次请求失败。跟1.1.2的测试比较,在并发用户数增大4倍之后,由于受CPU瓶颈影响,系统的吞吐量并没有增大,反而有略微的减少,并且用户等待时间从12.297ms增大到47.428ms,增大了4倍(结论四)。
[user@ServerA6 ab]$ ab -n 50000 -c 200 -p params -T application/x-www-form-urlencoded
http://172.26.125.117:7082/v1/routea/test/hello/leo
Time taken for tests: 11.857 seconds
Complete requests: 50000
Failed requests: 24
(Connect: 0, Receive: 0, Length: 24, Exceptions: 0)
Requests per second: 4216.93 [#/sec] (mean)
Time per request: 47.428 [ms] (mean)
Time per request: 0.237 [ms] (mean, across all concurrent requests)
测试中存在请求失败的情况,查询日志可以看到服务熔断的信息。那么,Zuul为什么会在Serivce正常的情况下出现服务熔断呢?这个记为问题二,同样在后续文章中进行解读。
Zuul资源使用情况:
压测过程中,Zuul服务器的CPU使用率为100%,堆内存的使用最大为500MB(堆空间为512MB)并且伴有频繁的GC,实时线程从79增加到269。
Service资源使用情况
压测过程中,Service服务器的CPU使用率为55%,堆内存的使用最大为390MB(堆空间为580MB),实时线程从49增加到80。可见Zuul请求转发的不及时,微服务端的CPU和内存都有富余(结论二)。通过线程数量的变化可知,Zuul端即使有200个线程转发用户请求,但是在Service端,仍然只有大概40个线程处理请求(问题一)。
2.2.1直接调用timeConsuming方法
系统吞吐量在 1000(请求/秒)左右,请求平均处理时间为1.017ms,请求平均等待时间为203.467ms,50000次请求都执行成功。跟1.2.1的测试比较,由于CPU和内存资源仍存在富余,在并发用户数增大4倍之后,系统的吞吐量增大了4倍(247.99提升到982.96),请求平均处理时间降低为四分之一(4.032ms缩减到1.017ms),请求平均等待时间基本没有变化(结论五)。
[user@ServerA6 ab]$ ab -n 50000 -c 200 http://172.26.125.115:8881/test/timeconsume/200
Time taken for tests: 50.867 seconds
Complete requests: 50000
Failed requests: 0
Requests per second: 982.96 [#/sec] (mean)
Time per request: 203.467 [ms] (mean)
Time per request: 1.017 [ms] (mean, across all concurrent requests)
Service资源使用情况
压测过程中,Service服务器的CPU使用率稳定在30%以内,堆内存的使用最大为370MB(堆空间扩充到640MB),实时线程从49增加到239。并且此时CPU和内存仍然有富余,系统的吞吐量可以随着并发线程的增加,同步增大。
2.2.2通过Zuul调用timeConsuming方法
系统吞吐量在2600(请求/秒)左右,请求平均处理时间为0.386ms,请求平均等待时间为77.109ms,50000次请求中有49196次请求出错,发生熔断(与2.1.2都发生服务熔断,只不过熔断的比例大幅度增加,问题二)。跟1.2.2的测试比较,在并发用户数增大4倍之后,由于发生熔断,Zuul服务器的CPU资源耗尽,系统的吞吐量虽然增加,但是请求出错,会造成不好的用户体验。但是Service端的CPU和内存的负荷会大幅度降低(结论六)。
[user@ServerA6 ab]$ ab -n 5000 -c 200 -p params -T application/x-www-form-urlencoded http://172.26.125.117:7082/v1/routea/test/timeconsume/200
Time taken for tests: 19.277 seconds
Complete requests: 50000
Failed requests: 49196
(Connect: 0, Receive: 0, Length: 49196, Exceptions: 0)
Requests per second: 2593.73 [#/sec] (mean)
Time per request: 77.109 [ms] (mean)
Time per request: 0.386 [ms] (mean, across all concurrent requests)
Zuul资源使用情况
压测过程中,Zuul服务器的CPU使用率接近100%,堆内存的使用最大为300MB(堆空间为512MB),实时线程从76增加到266。由于出现频繁的服务熔断,Zuul的CPU资源已经耗尽(结论六)。
Service资源使用情况
压测过程中,Service服务器的CPU使用率稳定在5%以内,堆内存的使用最大为280MB(堆空间为512MB),实时线程从48增加到88。可见发生服务熔断后,Service端的CPU和内存资源都有很大的释放(结论六)。
测试三:500个并发用户,执行50000次请求
3.1.1直接调用sayHello接口
系统吞吐量在5000(请求/秒)左右,请求平均处理时间为0.201ms,请求平均等待时间为100.580ms,50000次请求都执行成功。跟2.1.1的测试比较,在并发用户数增大2.5倍之后,由于CPU出现瓶颈,并且更多的并发用户带来额外的开销,系统的吞吐量开始下降,系统吞吐量从5465.68降到4971.14,并且用户等待时间从36.592ms增大到100.580ms。
[user@ServerA6 ab]$ ab -n 50000 -c 500 -p params -T application/x-www-form-urlencoded
http://172.26.125.115:8881/test/hello/leo
Time taken for tests: 10.058 seconds
Complete requests: 50000
Failed requests: 0
Write errors: 0
Requests per second: 4971.14 [#/sec] (mean)
Time per request: 100.580 [ms] (mean)
Time per request: 0.201 [ms] (mean, across all concurrent requests)
Service资源使用情况:
CPU使用率达到80%,堆内存的使用最大为450MB(堆空间扩充到710MB),实时线程从48增加到238。对比2.1.1的测试,为什么Service的线程没有随并发用户数的进一步增多而增大呢?(问题三),这个问题仍在后续文章中进行解释。
3.1.2通过路由Zuul调用sayHello接口(不做负载均衡)
系统吞吐量在4100(请求/秒)左右,请求平均处理时间为0.241ms,请求平均等待时间为120.743ms,50000次请求有92次熔断(问题二)。
[user@ServerA6 ab]$ ab -n 50000 -c 500 -p params -T application/x-www-form-urlencoded
http://172.26.125.117:7082/v1/routea/test/hello/leo
Time taken for tests: 12.074 seconds
Complete requests: 50000
Failed requests: 92
(Connect: 0, Receive: 0, Length: 92, Exceptions: 0)
Requests per second: 4141.04 [#/sec] (mean)
Time per request: 120.743 [ms] (mean)
Time per request: 0.241 [ms] (mean, across all concurrent requests)
Zuul资源使用情况:
压测过程中,Zuul服务器的CPU使用率为100%,堆内存的使用最大为330MB(堆空间为512MB)并且伴有频繁的GC,实时线程从77增加到268。这里和3.1.1一样,Zuul在请求的并发用户数达到500时,其并发处理线程仍保持在200了(问题三)。
Service资源使用情况
压测过程中,Service服务器的CPU使用率在50%以内,堆内存的使用最大为330MB(堆空间为580MB),实时线程从48增加到69。Zuul请求熔断,微服务端的CPU和内存都有富余。Service端,只有大概30个线程处理请求。
3.2.1直接调用timeConsuming方法
系统吞吐量在 1000(请求/秒)左右,请求平均处理时间为1.011ms,请求平均等待时间为505.538ms,50000次请求都执行成功。跟2.2.1的测试比较,在并发用户数增大2.5倍之后,系统的吞吐量增和请求平均处理时间基本没有变化,但是请求平均等待时间从203.467ms增大到530.300ms(结论四)。
[user@ServerA6 ab]$ ab -n 50000 -c 500 http://172.26.125.115:8881/test/timeconsume/200
Time taken for tests: 50.554 seconds
Complete requests: 50000
Failed requests: 0
Requests per second: 989.05 [#/sec] (mean)
Time per request: 505.538 [ms] (mean)
Time per request: 1.011 [ms] (mean, across all concurrent requests)
Service资源使用情况
压测过程中,Service服务器的CPU使用率稳定在30%以内,堆内存的使用最大为370MB(堆空间扩充到640MB),实时线程从49增加到239(问题三)。并且此时CPU和内存仍然有富余,系统的吞吐量可以随着并发线程的增加,同步增大。
3.2.2通过Zuul调用timeConsuming方法
系统吞吐量在2600(请求/秒)左右,请求平均处理时间为0.383ms,请求平均等待时间为191.269ms,50000次请求中有49196次请求出错,发生熔断(问题二)。与2.2.2的测试比较,在并发用户数增大2.5倍之后,系统的吞吐量增和请求平均处理时间基本没有变化,但是请求平均等待时间从77.109ms增大到191.269ms(结论四)。
[user@ServerA6 ab]$ ab -n 50000 -c 500 http://172.26.125.117:7082/v1/routea/test/timeconsume/200
Time taken for tests: 19.127 seconds
Complete requests: 50000
Failed requests: 49196
(Connect: 0, Receive: 0, Length: 49196, Exceptions: 0)
Requests per second: 2614.12 [#/sec] (mean)
Time per request: 191.269 [ms] (mean)
Time per request: 0.383 [ms] (mean, across all concurrent requests)
Zuul资源使用情况
压测过程中,Zuul服务器的CPU使用率接近100%,堆内存的使用最大为370MB(堆空间为512MB),实时线程从76增加到266(问题三)。由于出现频繁的服务熔断,Zuul的CPU资源已经耗尽。
Service资源使用情况
压测过程中,Service服务器的CPU使用率稳定在5%以内,堆内存的使用最大为340MB(堆空间为590MB),实时线程从48增加到88(问题一)。可见发生服务熔断后,Service端的CPU和内存资源都有很大的释放(结论六)。
本文总结
六个结论
在本文的三种压力测试过程中,我们得到了六个结论:
结论一:单台2核服务器能承受的最大吞吐量在5600左右。
结论二:在Zuul成为微服务架构的瓶颈时,由于请求转发的不及时,Service端的工作不饱和。因此要选择好Zuul的配置,避免出现性能瓶颈。
结论三:当Zuul的CPU高负荷运转时,其转发请求所带来的延迟就越高。因此要选择好Zuul的配置,尽可能降低Zuul转发带来的延迟。
结论四:在CPU成为瓶颈时,即使增大并发线程的数量,系统吞吐量也不会增大,反而会由于堆内存的开销变大,造成系统吞吐量的减少,并且用户等待时间会与并发线程数等比例增大。
结论五:在CPU和内存资源都充裕的情况下,增大并发线程的数量,系统的吞吐量会等比例增大,请求平均处理时间会随之降低,但请求平均等待时间不会改变,也就是用户体验并不会改变。
结论六:在高并发情况下如果Zuul发生服务熔断,Zuul服务器的CPU负荷会增大,甚至会耗尽;系统的吞吐量虽然增加,但是请求出错,会造成不好的用户体验。同时Service端的请求梳理会大幅度减少,其CPU和内存的负荷会大幅度降低。
三个问题
同样,我们也遇到了三个问题:
问题一:Zuul端转发请求的线程数与Service端处理请求的线程数之间是什么关系呢?
问题二:Zuul为什么会在Serivce正常的情况下出现服务熔断呢?
问题三:为什么Service的并发线程数量达到200后没有随并发用户数的进一步增大而增大呢?
下文,我们将针对这三个问题进行剖析,并通过参数调优解决高并发的处理问题。