SpringCloud從入門到進階(八)——單點部署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後沒有隨併發使用者數的進一步增大而增大呢?
下文,我們將針對這三個問題進行剖析,並通過引數調優解決高併發的處理問題。