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成功解決。
[[email protected] 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成功解決。
[[email protected] 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的執行緒切換,並且佔用更多的記憶體。因此係統吞吐量沒有等比例增大、平均等待時間有微小的波動,也在情理之中。
[[email protected] 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倍。
[[email protected] 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,與併發請求使用者數一致,問題三解決。