Spring 5 WebFlux 效能測試[譯]
Java
世界對反應式程式設計抱有很高的期望。 根據 ofollow,noindex">官方文件 的描述,它使程式員能夠構建更具彈性,彈性,響應和訊息驅動的應用程式。 簡而言之,它是一種更好,更快,更現代的模型,可以防止應用程式空閒。
Spring 5
通過結合基於 Project Reactor 的 Spring
反應計劃,引入了一種新的響應式程式設計模型。 但它能完成這項工作嗎?
我們研究了 Spring
提供的新功能,並對它進行了一次效能測試,測試結果請往下看,我想不會讓你失望的。
注意:我們的結果可能會在幾周/幾個月內改變。 實際上,截至目前,尚未釋出真實的 Spring
樣本,並且文件不完整。 Spring 5
和 Spring Boot 2
仍在開發中( Spring Framework 5.0.0 RC3
, Spring Boot 2.0.0.M2
), Project Reactor
也在不斷髮展。 此外,社群的反饋仍然很少( JHipster
, Spring
, Reddit
)。
What’s new in Spring 5?
Spring框架引入了很多新功能 。其中最重要的是反應式程式設計。
Spring MVC and Spring WebFlux
可能仍然有一些人可能試圖用舊的 Spring 4
技術進行反應式程式設計,如果你這樣做,那麼你肯定可能會遇到一些麻煩。 Spring 5
提供了一個易於使用的新模組: spring-webflux
。 它與它的兄弟 spring-mvc
做同樣的事情,但是它是一種響應式程式設計模型。 讓我們看看它是如何工作的吧。
WebFlux
主要圍繞兩個 Project Reactor
的類: Mono
和 Flux
。
Mono
是 CompletableFuture
型別的反應等價物,允許以反應方式處理單個物件。 Flux
是多個物件的等價物。 它們像 Stream
一樣處理(準備好使用 lambda
表示式)。 因此,你可能會看到如下所示的程式碼:
reactiveService.getResults() .mergeWith(Flux.interval(100)) .map(r -> r * 2) .doOnNext(Service1::someObserver) .doAfterTerminate(Service2::incrementTerminate);
它們都是 Reactive Streams
規範的 Publisher
介面的實現,因此它們需要註冊到訂閱伺服器以便資料開始流動。
幸運的是,基於註解的程式設計模型仍然是最新的,與 Spring MVC
的唯一區別是 REST
層的方法現在返回 Mono
或 Flux
:
Spring
知道如何處理 Monos
或 Fluxs
。 它會自動將封裝的物件傳遞給前端。
關於與資料庫的通訊, Spring 5
支援 Cassandra
, CouchBase
, MongoDB
和 Redis
的反應驅動程式,它們可以跟 Spring Data
一起使用。
下面是操作 MongoDB
程式碼例子
@Repository public interface BankAccountRepository extends ReactiveMongoRepository<BankAccount,String> { Mono<BankAccount> getFirstByBalanceEndingWith(BigDecimal bigDecimal); Mono<Long> countByBalanceEquals(BigDecimal bigDecimal); Flux<BankAccount> findAllByIdBefore(UUID uuid); }
Our tests
Why?
反應式程式設計現在正在流行,當然, Pivotal
決定在邏輯上將其整合到 Spring
框架中,並承諾提供更好的效能和可擴充套件性。 可悲的是,沒有給出任何效能測試資料……
How?
我們通過在生產模式下對不同的 JHipster
生成的應用程式( SQL/">MySQL
, Mongo
, Model
……)進行壓力測試(使用 Gatling
)。
這些應用程式中的每一個都經過多次複製和修改,以確保我們的測試有豐富的測試資料,從而確保測試的正確性。
例如,對於MySQL應用程式,我們建立了四個類似的應用程式:
- 使用
Spring 4
(因為你可以使用JHipster
實際生成) - 使用
Spring 5
(僅遷移) - 使用
Spring 5
和反應式程式設計(在REST
層上) - 使用
Spring 5
和反應式程式設計(僅在一個實體的RestController
類上)
對於具有非同步驅動程式的Mongo,我們建立了應用程式:
- 使用
Spring 4
(因為你可以使用JHipster
實際生成) - 使用
Spring 5
(僅遷移) - 使用
Spring 5
和反應式程式設計(在REST
層上) - 使用
Spring 5
和反應式程式設計(僅在一個實體的RestController
類上) - 使用
Spring 5
和實體上的反應式程式設計一直到儲存庫。
Spring
允許程式設計師配置自己的排程程式(處理被動呼叫的執行緒池)。 因此,當使用反應式程式設計時,僅在 REST
層(而不是實體)上,我們嘗試了不同的排程程式: Schedulers.parallel()
每個CPU核心使用一個執行緒,而 Schedulers.elastic()
動態建立執行緒。
每個測試包括同時啟動 Gatling 5000/10000/15000
使用者,每個使用者執行場景中描述的操作:
scenario("Test the Operation entity") .exec(http("First unauthenticated request") .get("/api/account") .headers(headers_http) .check(status.is(401))).exitHereIfFailed .pause(5) .exec(http("Authentication") .post("/api/authenticate") .headers(headers_http_authentication) .body(StringBody("""{"username":"admin", "password":"admin"}""")).asJSON .check(header.get("Authorization").saveAs("access_token"))).exitHereIfFailed .pause(1) .repeat(2) { exec(http("Authenticated request") .get("/api/account") .headers(headers_http_authenticated) .check(status.is(200))) .pause(5) } .repeat(2) { exec(http("Get all operations") .get("/api/operations") .headers(headers_http_authenticated) .check(status.is(200))) .pause(5 seconds, 10 seconds) .exec(http("Create new operation") .post("/api/operations") .headers(headers_http_authenticated) .body(StringBody("""{"id":null, "date":"2020-01-01T00:00:00.000Z", "description":"SAMPLE_TEXT", "amount":"1"}""")).asJSON .check(status.is(201)) .check(headerRegex("Location", "(.*)").saveAs("new_operation_url"))).exitHereIfFailed .pause(5) .repeat(8) { exec(http("Get created operation") .get("${new_operation_url}") .headers(headers_http_authenticated)) .pause(3) } .exec(http("Delete created operation") .delete("${new_operation_url}") .headers(headers_http_authenticated)) .pause(5) }
然後,我們可以通過比較時間或錯誤/崩潰來分析這些結果。
Our big configuration:
- 機器1用作
Spring Boot
伺服器和本地資料庫:i7-4790K 4GHz - 16Go - SSD - Ubuntu 16.04 64bits
- 機器2用作加特林客戶端:
i7-4790K 4GHz - 16Go - SSD - Ubuntu 16.04 64bits
-
Cisco SG100-24 24
埠千兆交換機
Results
從 5000
個使用者的模擬生成了以下結果。 我們還使用 10000/15000/20000
使用者進行了測試,但由於錯誤數量很多,結果並不一致。
With a MySQL-based JHipster application:
(注意:下面的結果不包括場景中的暫停。)

當用戶在他的Gatling場景中出現錯誤時,他的模擬將停止。 因此,如果存在一些錯誤,則請求伺服器的使用者較少,因此負載較低且時間更改。
錯誤可以有幾種:超時,達到資料庫連線的閾值,使用Spring建立/銷燬bean的併發問題,……
這些圖表顯示了使用者執行Gatling場景所需的總時間。

With a Mongo-based JHipster application:
Regarding the execution times
我們可以看到,總體而言, Reactive
應用程式比“經典” Spring
應用程式慢。
對於MySQL,它是可預測的,因為資料庫在使用過程中設定了所有鎖,並且沒有官方的響應/非同步驅動程式。
對於 Mongo
,有一堆完整的反應元件(驅動程式,儲存庫,……),但即便如此,效能也會更差。
此外,我們注意到 Spring 4
和 Spring 5
之間的速度沒有明顯改善,即使沒有新增反應式程式設計。
Regarding scalability
關於可擴充套件性, Reactive
應用程式可以處理比 Spring4
/ Spring5
應用程式更少的使用者。
實際上,我們通過使用 Gatling
模擬 5000,10000,15000
和 20000
使用者注意到了這種差異。
從 10000
個使用者開始,我們在 Reactive
應用程式上有太多錯誤,通常超過 40%
的 KO
請求。
Conclusion
- 我們的反應式應用程式沒有觀察到速度的提高(
Gatling
的結果甚至略差)。 - 關於使用者友好性,反應式程式設計不會新增大量新程式碼,但它肯定是一種更復雜的編碼(和除錯……)方式。 可能需要快速
Java 8
複習。 - 目前的主要問題是缺乏檔案。 這是我們生成測試應用程式的最大障礙,因此我們可能錯過了一個關鍵點。
- 因此,我們建議不要在反應式程式設計上跳得太快並等待更多反饋。
Spring WebFlux
尚未證明其優於Spring MVC
的優勢。
你可以在此儲存庫中找到我們的程式碼: jhipster / webflux-jhipster 。
Gatling
結果可以在每個模組根目錄的 gatling-results
目錄中找到。