SpringCloud從入門到進階——單點部署Zuul的壓力測試與調優(一)
- 2019 年 10 月 5 日
- 筆記
原文鏈接:https://www.cnblogs.com/lonelyJay/p/10076158.html
前言說明:通過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後沒有隨並發用戶數的進一步增大而增大呢?
下文,我們將針對這三個問題進行剖析,並通過參數調優解決高並發的處理問題。