內容
作為微服務架構系統的入口,毫無疑問,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,與並發請求用戶數一致,問題三解決。