SpringCloud从入门到进阶(九)——单点部署Zuul的压力测试与调优(二)
内容
作为微服务架构系统的入口,毫无疑问,Zuul的并发性能直接决定了整个系统的并发性能。本文结合前几篇文章的内容,在云服务器中部署了包含Eureka Server,Zuul等组件的1.0版本的微服务架构,并进行单点部署Zuul的压力测试,对其并发性能一探究竟。
环境
说明
转载请说明出处:SpringCloud从入门到进阶(九)——单点部署Zuul的压力测试与调优(二)
问题回顾
在上文中,我们在默认配置情况下,使用ApacheBench对微服务架构1.0中的Zuul和Service分别进行了压力测试,每次测试共50000个请求,并发用户数分别为50、200、500。在测试过程中我们遇到了如下三个问题:
问题一:Zuul端转发请求的线程数与后端Service处理请求的线程数不一致,它们之间是什么关系呢?
问题二:Zuul为什么会在Serivce正常的情况下出现服务熔断呢?
问题三:为什么后端Service的并发线程数量达到200后没有随并发用户数的进一步增大而增大呢?
下面,我们按照由易到难的顺序进行剖析这些问题,探究Zuul如何进行调优。
问题三
问题剖析
为什么后端Service的并发线程数量达到200后没有随并发用户数的增大而增大呢?
SpringBoot默认使用8.5版本的Tomcat作为内嵌的Web容器。因此Zuul或Service接收到的请求时,都是由Tomcat中Connector的线程池数量决定,也就是worker线程数。
Tomcat中默认的worker线程数的最大值为200(官方文档中有说明),可以在yaml中增加server.tomcat.max-threads属性来设置worker线程数的最大值。
配置调优
因此,问题三的解决方案是在Zuul端和Service端的yaml中增加如下配置:
#增大tomcat中worker的最大线程数量
server:
tomcat:
max-threads: 500
Service端完整的yaml配置文件:GitHub链接
问题二
问题剖析
为什么Zuul会在Serivce正常的情况下出现服务熔断呢?
默认情况下,当某微服务请求的失败比例大于50%(且请求总数大于20次)时,会触发Zuul中断路器的开启,后续对该微服务的请求会发生熔断,直到微服务的访问恢复正常。在Serivce正常时出现服务熔断,有可能是请求端或网络的问题,但通常是由于hystrix的信号量小于Zuul处理请求的线程数造成的。Zuul默认使用semaphores信号量机制作为Hystrix的隔离机制,当Zuul对后端微服务的请求数超过最大信号量数时会抛出异常,通过配置zuul.semaphore.max-semaphores可以设置Hystrix中的最大信号量数。也就是说zuul.semaphore.max-semaphores设置的值小于server.tomcat.max-threads,会导致hystrix的信号量无法被acquire,继而造成服务熔断。
问题解决
确保zuul.semaphore.max-semaphores属性值大于server.tomcat.max-threads。
问题一
问题剖析
Zuul端转发请求的线程数与后端Service处理请求的线程数之间是什么关系呢?
Zuul集成了Ribbon与Hystrix,当使用Service ID配置Zuul的路由规则时,Zuul会通过Ribbon实现负载均衡,通过Hystrix实现服务熔断。这个过程可以理解为这三个动作:Zuul接收请求,Zuul转发请求,Service接收请求。其中第一个和第三个动作,由问题三可知,分别由Zuul和Service的server.tomcat.max-threads属性配置。
第二个动作使用了Ribbon实现负载均衡,通过设置ribbon.MaxConnectionsPerHost属性(默认值50)和ribbon.MaxTotalConnections属性(默认值200)可以配置Zuul对后端微服务的最大并发请求数,这两个参数分别表示单个后端微服务实例请求的并发数最大值和所有后端微服务实例请求并发数之和的最大值。
第二个动作同时使用Hystrix实现熔断,Zuul默认使用semaphores信号量机制作为Hystrix的隔离机制,当Zuul对后端微服务的请求数超过最大信号量数时会抛出异常,通过配置zuul.semaphore.max-semaphores可以设置Hystrix中的最大信号量数。
因此通过配置上述三个属性可以增加每个路径下允许转发请求的线程数。这三个属性的关系用下图粗略的进行表示:
Zuul端转发请求的线程数与Service端处理请求的线程数的关系:
限制一:单点部署的Zuul同时处理的最大线程数为server.tomcat.max-threads;
限制二:向所有后端Service同时转发的请求数的最大值为server.tomcat.max-threads、ribbon.MaxTotalConnections和zuul.semaphore.max-semaphores的最小值,这也是所有后端Service能够同时处理请求的最大并发线程数;
限制三:单个后端Service能同时处理的最大请求数为其server.tomcat.max-threads和ribbon.MaxConnectionsPerHost中的最小值。
注意:很多博客提到使用zuul.host.maxTotalConnections与zuul.host.maxPerRouteConnections这两个参数。经过查阅和实践,这两个参数在使用Service ID配置Zuul的路由规则时无效,只适用于指定微服务的url配置路由的情景。
配置调优
在Zuul端的yaml配置文件中增加如下配置,为了避免因为等待时间过长造成请求处理失败,增加Ribbon和Hystrix的超时设置:
ribbon:
#Ribbon允许最大连接数,即所有后端微服务实例请求并发数之和的最大值。
MaxTotalConnections: 500
#单个后端微服务实例能接收的最大请求并发数
MaxConnectionsPerHost: 500
#建议设置超时时间,以免因为等待时间过长造成请求处理失败(一)
#Http请求中的socketTimeout
ReadTimeout: 5000
#Http请求中的connectTimeout
ConnectTimeout: 10000
#hystrix信号量semaphore的设置,默认为100,决定了hystrix并发请求数
zuul:
semaphore:
max-semaphores: 500
#建议设置超时时间,以免因为等待时间过长造成请求处理失败(二)
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 10000
Zuul端完整的yaml配置文件:GitHub链接
再测2.1.2通过Zuul调用sayHello接口(200并发用户数)
系统吞吐量达到了4200左右,请求平均处理时间为0.236ms,请求平均等待时间为47.165ms,50000次请求全部成功
结果:请求全部成功,问题2成功解决。
[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.791 seconds
Complete requests: 50000
Failed requests: 0
Requests per second: 4240.46 [#/sec] (mean)
Time per request: 47.165 [ms] (mean)
Time per request: 0.236 [ms] (mean, across all concurrent requests)
Zuul资源使用情况:
压测过程中,Zuul服务器的CPU使用率为100%,堆内存的使用最大为500MB(堆空间为512MB)并且伴有频繁的GC,实时线程从79增加到269。
Service资源使用情况
压测过程中,Service服务器的CPU使用率为55%,堆内存的使用最大为390MB(堆空间为580MB),实时线程从49增加到80。
再次测试3.1.2通过路由Zuul调用sayHello接口(500并发用户数)
系统吞吐量在4000(请求/秒)左右,请求平均处理时间为0.246ms,请求平均等待时间为123.168ms,50000次请求全部成功。相对于上一节中的3.1.2,在并发用户数增大2.5倍之后,系统的吞吐量有略微的减小。
结果:请求全部成功,问题2成功解决。
[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.317 seconds
Complete requests: 50000
Failed requests: 0
Requests per second: 4059.48 [#/sec] (mean)
Time per request: 123.168 [ms] (mean)
Time per request: 0.246 [ms] (mean, across all concurrent requests)
Zuul资源使用情况:
压测过程中,Zuul服务器的CPU使用率为100%,堆内存的使用最大为470MB(堆空间为512MB)并且伴有频繁的GC,实时线程从71增加到492。此时CPU和内存都存在瓶颈。
结果:Zuul接收请求的线程数超过了200,达到了430+,问题三解决。
Service资源使用情况
压测过程中,Service服务器的CPU使用率在50%以内,堆内存的使用最大为330MB(堆空间为580MB),实时线程从48增加到89,将近50个线程在处理Zuul转发的请求。
再测3.2.1直接调用timeConsuming方法(500并发用户数)
系统吞吐量在2400(请求/秒)左右,请求平均处理时间为0.423ms,请求平均等待时间为211.451ms,50000次请求都执行成功。跟上一节3.2.1的测试比较,在并发用户数增大2.5倍之后,系统的吞吐量同步增大将近2.4倍,请求平均等待时间从203.467ms变为211.451ms。由于线程增加会增大CPU的线程切换,并且占用更多的内存。因此系统吞吐量没有等比例增大、平均等待时间有微小的波动,也在情理之中。
[user@ServerA6 ab]$ ab -n 50000 -c 500 http://172.26.125.115:8881/test/timeconsume/200
Time taken for tests: 21.145 seconds
Complete requests: 50000
Failed requests: 0
Requests per second: 2364.61 [#/sec] (mean)
Time per request: 211.451 [ms] (mean)
Time per request: 0.423 [ms] (mean, across all concurrent requests)
Service资源使用情况
压测过程中,Service服务器的CPU使用率稳定在50%以内,堆内存的使用最大为470MB(堆空间扩充到670MB),实时线程从40增加到530。此时CPU和内存仍然有富余,因此系统的吞吐量还会随着并发线程的增加而同步增大,感兴趣的童鞋可以尝试将server.tomcat.max-threads属性设置成1000进行测试。
结果:实时线程从40增加到530,有500个线程在同时处理请求。问题三解决。
再次测试3.2.2通过Zuul调用timeConsuming方法(500并发用户数)
系统吞吐量在2200(请求/秒)左右,请求平均处理时间为1.762ms,请求平均等待时间为880.781ms,50000次请求中有47082次请求出错,发生熔断(问题二)。跟1.2.2的测试情况相比,在并发用户数增大10倍之后,系统的吞吐量同步增长9倍。
[user@ServerA6 ab]$ ab -n 50000 -c 500 http://172.26.125.117:7082/v1/routea/test/timeconsume/200
Time taken for tests: 23.348 seconds
Complete requests: 50000
Failed requests: 0
Requests per second: 2141.47 [#/sec] (mean)
Time per request: 233.485 [ms] (mean)
Time per request: 0.467 [ms] (mean, across all concurrent requests)
Zuul资源使用情况
压测过程中,Zuul服务器的CPU使用率在65%附近波动,堆内存的使用最大为370MB(堆空间为512MB),实时线程从70增加到560。Zuul服务器的CPU和内存资源还有富余。
Service资源使用情况
压测过程中,Service服务器的CPU使用率在35%附近波动,堆内存的使用最大为420MB(堆空间为650MB),实时线程从48增加到538。Service服务器的CPU和内存资源还有富余。
结果:Service端的处理线程数为500,与并发请求用户数一致,问题三解决。