1. 引言
在本教程中,我們將探討 Spring MVC 中 <em @Async</em> 註解,並隨後熟悉 Spring WebFlux。 我們的目標是更好地理解這兩種技術的區別。
2. 實施場景
這裏,我們將選擇一個場景來展示如何使用這些 API 實現一個簡單的 Web 應用程序。我們尤其感興趣的是在每種情況下,瞭解線程管理和阻塞或非阻塞 I/O 的更多信息。
讓我們選擇一個具有一個返回字符串結果的端點的 Web 應用程序。這裏的關鍵在於,請求將通過一個 Filter,延遲 200 毫秒,然後 Controller 需要 500 毫秒來計算並返回結果。
接下來,我們將使用 Apache ab 在兩個端點上模擬負載,並使用 JConsole 監控應用程序的行為。
值得一提的是,在本文中,我們的目標不是對這兩個 API 進行基準測試,而只是一個小規模的負載測試,以便我們能夠跟蹤線程管理。
3. Spring MVC 異步處理
Spring 3.0 引入了 @Async 註解。 @Async 的目標是允許應用程序在單獨的線程上運行高負載的任務。 調用者也可以在感興趣時等待結果。 因此,返回值不能為 void, 必須是 Future、CompletableFuture 或 ListenableFuture 中的一種。
此外,Spring 3.2 引入了 org.springframework.web.context.request.async 包,與 Servlet 3.0 一起,將異步處理的樂趣帶到了 Web 層。
因此,自 Spring 3.2 以來,@Async 可以用於標記為 @Controller 或 @RestController 的類。
當客户端發起請求時,請求會經過過濾器鏈中所有匹配的過濾器,直到到達 DispatcherServlet 實例。
然後,servlet 負責異步調度請求。 它通過調用 AsyncWebRequest#startAsync 開始請求,將請求處理任務轉移到 WebSyncManager 實例,並在不提交響應的情況下完成任務。 過濾器鏈也以相反的方向遍歷到根目錄。
WebAsyncManager 將請求處理任務提交到其關聯的 ExecutorService。
4. Spring Async 實現
讓我們通過編寫我們的應用程序類 AsyncVsWebFluxApp 開始實現。 @EnableAsync 負責為我們的 Spring Boot 應用程序啓用異步功能:
@SpringBootApplication
@EnableAsync
public class AsyncVsWebFluxApp {
public static void main(String[] args) {
SpringApplication.run(AsyncVsWebFluxApp.class, args);
}
}然後我們有 AsyncFilter, 它實現了 javax.servlet.Filter。請記住在 doFilter 方法中模擬延遲:
@Component
public class AsyncFilter implements Filter {
...
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
// sleep for 200ms
filterChain.doFilter(servletRequest, servletResponse);
}
}最後,我們使用“/async_result”端點開發了我們的 AsyncController:
@RestController
public class AsyncController {
@GetMapping("/async_result")
@Async
public CompletableFuture getResultAsyc(HttpServletRequest request) {
// sleep for 500 ms
return CompletableFuture.completedFuture("Result is ready!");
}
}由於上述的 @Async,該方法將在應用程序的默認 ExecutorService 中以一個單獨的線程執行。但是,為了我們的方法,也可以設置一個特定的 ExecutorService。
測試時間!讓我們運行應用程序,安裝 Apache ab,或者任何工具來模擬負載。然後我們可以向“async_result”端點發送大量的併發請求。我們可以執行 JConsole 並將其附加到我們的 Java 應用程序以監控進程:
ab -n 1600 -c 40 localhost:8080/async_result
5. Spring WebFlux
Spring 5.0 引入了 WebFlux 以非阻塞方式支持響應式 Web。WebFlux 基於 Reactor API,是 響應式流 的又一種出色實現。
WebFlux 支持響應式背壓以及 Servlet 3.1+,並利用其非阻塞 I/O。因此,它可以運行在 Netty、Undertow、Jetty、Tomcat 或任何 Servlet 3.1+ 兼容服務器上。
雖然所有服務器不使用相同的線程管理和併發控制模型,但只要它們支持非阻塞 I/O 和響應式背壓,WebFlux 就能正常工作。
WebFlux 允許我們以聲明式的方式分解邏輯,使用 Mono, Flux 以及它們豐富的操作符集。 此外,我們還可以擁有除了帶有 @Controller 註解的端點之外的其他功能性端點,儘管我們現在也可以在 Spring MVC 中使用它們。
6. Spring WebFlux 實現
為了實現 WebFlux,我們遵循異步編程的路徑。首先,我們創建 AsyncVsWebFluxApp:
@SpringBootApplication
public class AsyncVsWebFluxApp {
public static void main(String[] args) {
SpringApplication.run(AsyncVsWebFluxApp.class, args);
}
}然後我們來編寫我們的 WebFluxFilter,它實現了 WebFilter。 我們將生成一個人為的延遲,然後將請求傳遞到過濾器鏈中:
@Component
public class WebFluxFilter implements org.springframework.web.server.WebFilter {
@Override
public Mono filter(ServerWebExchange serverWebExchange, WebFilterChain webFilterChain) {
return Mono
.delay(Duration.ofMillis(200))
.then(
webFilterChain.filter(serverWebExchange)
);
}
}最後,我們擁有了 WebFluxController。它暴露了一個名為 “/flux_result” 的端點,並返回一個 Mono<String> 作為響應:
@RestController
public class WebFluxController {
@GetMapping("/flux_result")
public Mono getResult(ServerHttpRequest request) {
return Mono.defer(() -> Mono.just("Result is ready!"))
.delaySubscription(Duration.ofMillis(500));
}
}對於本次測試,我們採用與異步示例應用程序相同的策略。以下是示例結果:
ab -n 1600 -c 40 localhost:8080/flux_result7. 區別分析
Spring Async 支持 Servlet 3.0 規範,而 Spring WebFlux 支持 Servlet 3.1+。 這帶來了一系列差異:
- Spring Async 在與客户端通信期間採用阻塞式 I/O 模型,可能導致與慢速客户端的性能問題。 相反,Spring WebFlux 提供非阻塞式 I/O 模型。
- 在 Spring Async 中,讀取請求體或請求部分是阻塞操作,而在 Spring WebFlux 中則是非阻塞操作。
- 在 Spring Async 中,Filter 和 Servlet 是同步工作,而 Spring WebFlux 支持完整的異步通信。
- Spring WebFlux 與 Spring Async 兼容的 Web/Application 服務器範圍更廣,例如 Netty 和 Undertow。
此外,Spring WebFlux 支持 reactive backpressure,因此我們對快速生產者如何響應擁有更多的控制,優於 Spring MVC Async 和 Spring MVC。
Spring WebFlux 還通過 Reactor API 推動了編程風格向函數式編程的轉變,以及聲明式 API 分解。
所有這些因素是否會導致我們使用 Spring WebFlux? 答案是:Spring Async 甚至 Spring MVC 可能會是許多項目中正確的選擇,具體取決於系統的負載可伸縮性和可用性。
在可伸縮性方面,Spring Async 的結果優於同步的 Spring MVC 實現。 Spring WebFlux,由於其反應式特性,為我們提供了彈性以及更高的可用性。
8. 結論
在本文中,我們更深入地瞭解了 Spring Async 和 Spring WebFlux,並通過一個基本的負載測試,對它們進行了理論和實踐上的比較。