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