内容
作为微服务架构系统的入口, 毫无疑问, Zuul 的并发性能直接决定了整个系统的并发性能. 本文结合前几篇文章的内容, 在云服务器中部署了包含 Eureka Server,Zuul 等组件的 1.0 版本的微服务架构, 并进行单点部署 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, 与并发请求用户数一致, 问题三解决.
来源: https://www.cnblogs.com/lonelyJay/p/10076441.html