在什麼情況下?
我當時正在使用 Spring Boot 2.x.x 版本的 Webflux。
當我在代碼中使用 WebClient 時,通常會在請求後使用 exchange()。因為我所使用的 API 在失敗時,仍會回傳 200 HTTP Status 並帶有錯誤主體(error body)。
當時我犯了一個大錯,我以為 exchange() 和 retrieve() 是相同的。
如果你也是這麼想的,那你就錯了。它們是不同的。那麼 exchange 和 retrieve 之間到底有什麼區別呢?
回應方式不同
根據 官方文件:
- WebClient.ResponseSpec
retrieve()- 當回應狀態為 200 OK 時的回應主體。
- 預設情況下,4xx 和 5xx 的回應會導致
WebClientResponseException。若要自定義錯誤處理,請使用onStatus處理器。
- **reactor.core.publisher.Mono
`exchange()`** - 自 Spring 5.3 起已棄用,因為可能導致記憶體和/或連線洩漏。
- 使用
exchange()時,應用程式有責任消耗(consume)任何回應內容,無論是成功、錯誤或非預期數據等情況。若不這樣做,可能會導致記憶體洩漏。 - 請改用
exchangeToMono(Function)或exchangeToFlux(Function)。
exchangeToMono,exchangeToFlux- 自 Spring 5.3 以上版本提供。
- 自 Spring Boot 2.4.x 以上版本提供。
何時會發生記憶體洩漏?
當您未在代碼中使用 ClientResponse.bodyToMono 或 bodyToFlux 時。
.exchange()
.flatMap { clientResponse ->
if (clientResponse.statusCode() === HttpStatus.OK) {
// 請求成功
clientResponse.bodyToMono(CLASS::class.java)
}
else {
// HTTP 請求失敗。這裡會發生記憶體洩漏。
throw Exception()
}
}
在失敗的情況下,您將面臨記憶體洩漏,因為您沒有消耗(consume)回應主體。
如果您沒有消耗回應主體,您的連線將不會斷開。
接下來會發生什麼事呢?您的 HTTP 連線池(connection pool)將被已使用的執行緒填滿。當沒有更多可用執行緒時,您的請求將會失敗。
如何解決?
如果您使用的是 Spring Boot 2.4.x 以上的版本,請像下面這樣使用 exchangeToMono:
.exchangeToMono(response -> {
if (response.statusCode().equals(HttpStatus.OK)) {
return response.bodyToMono(Person.class);
}
else if (response.statusCode().is4xxClientError()) {
return response.bodyToMono(ErrorContainer.class);
}
else {
return Mono.error(response.createException());
}
});
如果您使用的是 Spring Boot 2.4.x 以下的版本,請使用 retrieve(),或在拋出異常前消耗您的回應主體。
例如以下代碼:
clientResponse.bodyToMono<String>().defaultIfEmpty("").map {
throw Exception()
}
結論
我認為在開始使用函式庫之前,應該養成仔細閱讀並正確使用文件的習慣。 函式庫並不會幫你處理好所有事情。
求知若渴,虛懷若谷 (Stay Hungry, Stay Foolish)