是時候考慮Spring非阻塞編程模式
導讀:Spring框架中,同時存在WebFlux和R2DBC這樣的響應式模塊,也存在Web MVC和JDBC這樣的阻塞框架。應該在什么情況下使用不同技術棧,可能會困擾很多技術人。本文作者對這兩種技術棧做了詳細的對比和壓力測試,為技術選型提供支持。
2017年9月發布的Spring Framework 5中,引入了Spring WebFlux。WebFlux是完全響應式的技術棧。2019年12月發布了Spring Data R2DBC,這是一個使用響應式的數據庫驅動。在本文中,我將證明在高并發下,WebFlux和R2DBC表現更好。該組合的響應時間和吞吐量都更好。并且在處理每個請求時使用更少的內存和CPU,而且你的Fat JAR會變得更小。在高并發的時候,使用WebFlux和R2DBC(如果你不需要JPA的話)是個好主意。
測試方法
本文中,我們嘗試了如下四種組合:
- Spring Web MVC + JDBC 數據庫驅動
- Spring Web MVC + R2DBC 數據庫驅動
- Spring WebFlux + JDBC 數據庫驅動
- Spring WebFlux + R2DBC 數據庫驅動
我已經將并行請求數以50個為單位從4增加到500,分別為負載生成器和服務分配4個核心(我的筆記本有12個核心)。我將所有的連接池都配置為100。為什么要固定核數和連接池的大小?因為在之前對JDBC和R2DBC的測試中,改變這些因素并沒有提供更多的數據,所以我決定在這個測試中保持固定的變量,以減少測試需要運行的時間。
我在服務上模擬一個GET請求。該服務從數據庫中獲取了10條記錄,并以JSON形式返回。首先,我對服務進行了2秒預熱。接下來,我開始了1分鐘的基準測試。我把每個場景運行5次(依次運行,而非5次之后再運行其他測試),并計算結果的平均值。我只統計了那些沒有錯誤的測試。當我將并發數增加到1000以上時,所有的實現都無一例外地有失敗。
我使用了Postgres(12.2)作為數據庫。并且使用wrk來進行基準測試。我用下面的方法解析wrk的輸出。主要測量:
- 響應時間—來自Wrk測試報告
- 吞吐量(請求數)— 來自Wrk測試報告
- 進程CPU的使用情況—用戶和內核時間(基于/proc/PID/Stat)
- 內存使用量—私有和共享進程內存(基于/proc/PID/maps)
你可以在這里查看所使用的測試腳本[1]。你可以在這里查看所使用的代碼[2]。
測試結果
你可以在這里查看我在圖表中使用的原始數據[3]。
響應時間
很顯然在高并發下,Spring Web MVC + JDBC可能不是你的最佳選擇。顯然在更高的并發下,R2DBC可以提供更好的響應時間。Spring Web MVC和Spring WebFlux也有類似的趨勢。
吞吐量
與響應時間類似,使用JDBC+Spring Web MVC在高并發下表現得更差。同樣的,R2DBC顯然表現的更勝一籌。如果你的后端仍然使用JDBC,那么從Spring Web MVC轉移到Spring WebFlux也并不是一個好主意。在低并發時,Spring Web MVC + JDBC 表現最好。
CPU
CPU是指整個運行過程中的CPU時間,即進程用戶和內核時間之和。
使用JDBC+Web MVC的方案在高并發時消耗的CPU最高。JDBC+WebFlux的方案使用的CPU時間最少,但吞吐量也最低。當你查看平均每請求所使用的CPU時,你就可以衡量各種方式的CPU使用效率。
與JDBC相比,R2DBC平均每個請求使用的CPU更少。使用JDBC+WebFlux似乎不是一個好主意。JDBC+Web MVC在高并發量的情況下更加糟糕,而其他至少有一個非阻塞組件的實現更穩定。然而在低并發時,Web MVC + JDBC可以最有效地利用CPU。
內存
我們測量在運行結束時進程私有內存作為內存消耗量。內存使用情況取決于垃圾回收。我們使用JDK 11.0.6和G1GC。Xms 設置為 0.5 Gb (默認是我可用內存32 Gb 的 1/64)。Xmx 設置為 8 Gb (默認是我可用內存 32 Gb 的 1/4)。
與Web MVC相比,WebFlux的內存使用量似乎更穩定,而WebMVC在高并發時的內存使用量更大。當使用WebFlux+R2DBC時,在高并發情況下,內存使用量最少。在低并發時,Web MVC + JDBC內存使用較低,但在高并發時,WebFlux + R2DB平均每個請求處理中使用的內存最少。
Fat Jar大小
下圖中JPA占用了大頭。如果你使用R2DBC的情況下不使用它,那么Fat JAR大小就會下降到15Mb左右!
總結
- R2DBC+WebFlux是高并發時的好主意!
- 在高并發時,使用R2DBC代替JDBC和使WebFlux代替Web MVC的好處顯而易見。
- 處理單個請求所需的CPU更少。
- 處理單個請求所需的內存更少。
- 高并發時的響應時間更低。
- 高并發時的吞吐量更好
- Fat JAR較小(不使用JPA)。
- 當只使用阻塞組件時,在高并發時,內存和CPU的使用效率會降低。
- JDBC+WebFlux似乎不是一個好主意。R2DBC+Web MVC在高并發時比JDBC+Web MVC效果更好。
- 你不需要使用完全無阻塞的堆棧來獲得使用R2DBC的優勢。但是,如果是使用Spring,最好將其與WebFlux結合起來。
- 在低并發量(200個并發請求以下)時,使用Web MVC和JDBC可能會有更好的效果。通過測試來確定平衡點。
使用R2DBC時的一些挑戰
- JPA無法處理像Spring Data R2DBC提供響應式功能。這意味著在使用R2DBC時,你將不得不手動做更多工作。
- 還有其他響應式驅動,例如Quarkus Reactive Postgres客戶端(使用Vert.x)。他們不使用R2DBC,并且有不同的性能特性。
- 有限的可用性
- 不是所有的關系型數據庫都有響應式的驅動程序。例如,Oracle還沒有R2DBC實現。
- 應用服務器仍然依賴于JDBC。在這個Kubernetes時代,人們還在使用那些上古功能嗎?
- 當Java Fibers推出的時候(Project Loom,可能是Java 15),數據庫驅動的格局可能會再次發生變化,R2DBC可能不會成為JDBC的繼任者。
原文地址:
https://technology.amis.nl/2020/04/10/spring-blocking-vs-non-blocking-r2dbc-vs-jdbc-and-webflux-vs-web-mvc/
文中鏈接:
[1]https://github.com/MaartenSmeets/db_perftest/blob/r2dbc/test_scripts/run_test.py
[2]https://github.com/MaartenSmeets/db_perftest/tree/r2dbc/test_apps
[3]https://github.com/MaartenSmeets/db_perftest/blob/r2dbc/test_scripts/restotal.txt
本文轉載自微信公眾號「高可用架構」,可以通過以下二維碼關注。轉載本文請聯系高可用架構公眾號。