一、前言
- 響應式程式設計是啥?
- 為啥要有響應式程式設計?
- 響應式流的核心機制是什麼?
- Spring 響應式程式設計能解決我們平時開發的什麼痛點?
- Spring 響應式程式設計有哪些應用場景?
- Spring 響應式程式設計未來的趨勢如何?
開篇六連問,等咱們熟悉完再來真香也不遲,我們廢話少說,直接來暢遊 Spring 響應式程式設計的世界。
二、響應式程式設計是啥?
在計算中,響應式程式設計或反應式程式設計(Reactive programming)是一種面向資料串流和變化傳播的聲明式程式設計範式。這意味着可以在程式設計語言中很友善地表達靜态或動态的資料流,而相關的計算模型會自動将變化的值通過資料流進行傳播。
有點抽象?沒有關系,老周這就來說道說道。核心的一點響應式程式設計是聲明式程式設計範式,對指令式程式設計進行替代的一個範例,這種替代的存在是因為響應式程式設計解決了指令式程式設計的限制。大多數開發者都是指令式程式設計起步的,你寫的代碼就是一行接一行的指令,按照它們的順序一次一條地出現。一個任務被執行,程式就需要等到它執行完了,才能執行下一個任務。每一步,資料都需要完全擷取到了才能被處理,是以它需要作為一個整體來處理。
指令式程式設計有個最大的弊端是:當正在執行的任務被阻塞了,特别是一個 IO 任務,例如将資料寫入到資料庫或從遠端伺服器擷取資料,那麼調用該任務的線程将無法做任何事情,直到任務完成。說白了,阻塞的線程就是一種浪費,在如今的環境,線程的資源是那麼的寶貴。
相反,響應式程式設計是函數式和聲明式的。響應式程式設計涉及描述通過該資料流的 pipeline 或 stream,而不是描述的一組按順序執行的步驟。響應式流處理資料時隻要資料是可用的就進行處理,而不是需要将資料作為一個整體進行提供。
三、為啥要有響應式程式設計?
我們上面也說了指令式程式設計會線程阻塞,而響應式程式設計是聲明式程式設計範式的,是對指令式程式設計進行替代的一個範例。
對于指令式程式設計的同步阻塞,其實業界是有一些處理方案的,比如在 Java 中,為了實作異步非阻塞,一般會采用回調和 Future 這兩種機制,但這兩種機制都存在一定局限性。
3.1 回調機制
我們來看下面這個圖:
服務 B 的 methodB() 方法調用服務 A 的 methodA() 方法,然後服務 A 的 methodA() 方法執行完畢後,再主動調用服務 B 的 callback() 方法。
回調展現的是一種雙向的調用方式,實作了服務 A 和服務 B 之間的解耦。在這個 callback 回調方法中,回調的執行是由任務的結果來觸發的,是以我們就可以異步來執行某項任務,進而使得調用鍊路不發生任何的阻塞。
回調的最大問題是複雜性,一旦在執行流程中包含了多層的異步執行和回調,那麼就會形成一種嵌套結構,給代碼的開發和調試帶來很大的挑戰。是以回調很難大規模地組合起來使用,因為很快就會導緻代碼難以了解和維護,進而造成所謂的“回調地獄”問題。之前公司就遇到代碼“回調地獄”問題,十幾層的回調,後面的人進來維護估計會吐。
3.2 Future 機制
我們再來看看 Future 這種機制,有一個需要處理的任務,然後把這個任務送出到 Future,Future 就會在一定時間内完成這個任務,而在這段時間内我們可以去做其他事情。下面我們來看看來自 Doug Lea 大神在 Java 中的 Future 接口設計:
我們可以看到,大神在上面的設計來達到一定的異步執行效果。但從本質上講,Future 以及由 Future 所衍生出來的 CompletableFuture 等各種優化方案就是一種多線程技術。多線程假設一些線程可以共享一個 CPU,而 CPU 時間能在多個線程之間共享,這一點就引入了“上下文切換”的概念。
如果想要恢複線程,就需要涉及加載和儲存寄存器等一系列計算密集型的操作。是以,大量線程之間的互相協作同樣會導緻資源利用效率低下。
3.3 響應式程式設計實作方法
3.3.1 資料流與響應式
資料流就是資料像水流一樣源源不斷的輸入過來,而系統的響應能力就展現在對這些資料流的即時響應過程上。我們可以不采用傳統的同步調用方式來處理資料,而是由處于資料庫上遊的各層元件自動來執行事件,從web到service再到dao層,這個過程就像水流一樣,整個資料傳遞鍊路都應該是采用事件驅動的方式來進行運作的,這個過程都應該是異步非阻塞的,這就是響應式程式設計的核心特點。
相較傳統開發所普遍采用的“拉”模式,在響應式程式設計下,基于事件的觸發和訂閱機制,這就形成了一種類似“推”的工作方式。說白了,就類似現在的 Kafka 等消息引擎,大部分都采用事件驅動的 pub/sub 模式的架構。這種模式的最大優勢是生成事件和消費事件的過程是異步執行的,意味着資源之間的競争關系較少,故伺服器的響應能力也就越高。
3.3.2 響應式宣言
響應式宣言是一份建構現代雲擴充架構的處方。這個架構主要使用消息驅動的方法來建構系統,在形式上可以達到彈性和韌性,最後可以産生響應性的價值。所謂彈性和韌性,通俗來說就像是橡皮筋,彈性是指橡皮筋可以拉長,而韌性指在拉長後可以縮回原樣。
- 響應性: :隻要有可能,系統就會及時地做出響應。即時響應是可用性和實用性的基石,而更加重要的是,即時響應意味着可以快速地檢測到問題并且有效地對其進行處理。即時響應的系統專注于提供快速而一緻的響應時間,确立可靠的回報上限,以提供一緻的服務品質。這種一緻的行為轉而将簡化錯誤處理、建立最終使用者的信任并促使使用者與系統作進一步的互動。
- 韌性:系統在出現失敗時依然保持即時響應性。這不僅适用于高可用的、任務關鍵型系統——任何不具備回彈性的系統都将會在發生失敗之後丢失即時響應性。回彈性是通過複制、遏制、隔離以及委托來實作的。失敗的擴散被遏制在了每個元件内部,與其他元件互相隔離,進而確定系統某部分的失敗不會危及整個系統,并能獨立恢複。每個元件的恢複都被委托給了另一個(外部的)元件,此外,在必要時可以通過複制來保證高可用性。(是以)元件的用戶端不再承擔元件失敗的處理。
- 彈性: 系統在不斷變化的工作負載之下依然保持即時響應性。反應式系統可以對輸入(負載)的速率變化做出反應,比如通過增加或者減少被配置設定用于服務這些輸入(負載)的資源。這意味着設計上并沒有争用點和中央瓶頸,得以進行元件的分片或者複制,并在它們之間分布輸入(負載)。通過提供相關的實時性能名額,反應式系統能支援預測式以及反應式的伸縮算法。這些系統可以在正常的硬體以及軟體平台上實作成本高效的彈性。
- 消息驅動:反應式系統依賴異步的消息傳遞,進而確定了松耦合、隔離、位置透明的元件之間有着明确邊界。這一邊界還提供了将失敗作為消息委托出去的手段。使用顯式的消息傳遞,可以通過在系統中塑造并監視消息流隊列,并在必要時應用回壓,進而實作負載管理、 彈性以及流量控制。使用位置透明的消息傳遞作為通信的手段, 得跨叢集或者在單個主機中使用相同的結構成分和語義來管理失敗成為了可能。非阻塞的通信使得接收者可以隻在活動時才消耗資源,進而減少系統開銷。
問題:消息驅動與上面提到的事件驅動有啥差別呢?
響應式宣言指出了兩者的差別:“消息驅動”中消息資料被送往明确的目的位址,有固定導向;“事件驅動”是事件向達到某個給定狀态的元件發出的信号,沒有固定導向,隻有被觀察的資料。
- 在一個消息驅動系統中,可尋址的接收者等待消息的到來然後響應消息,否則保持休眠狀态,消息驅動系統專注于可尋址的接收者。響應式系統更加關注分布式系統的通信和協作以達到解耦、異步的特性,滿足系統的彈性和容錯性,是以響應式系統更傾向于使用消息驅動模式。
- 在一個事件驅動系統中,通知的監聽者被綁定到消息源上。這樣當消息被發出時,它就會被調用,是以,響應式程式設計更傾向于事件驅動。
WebFlux 簡介
WebFlux 是 Spring Framework5.0 中引入的一種新的反應式Web架構。通過Reactor項目實作Reactive Streams規範,完全異步和非阻塞架構。本身不會加快程式執行速度,但在高并發情況下借助異步IO能夠以少量而穩定的線程處理更高的吞吐,規避檔案IO/網絡IO阻塞帶來的線程堆積。
1.1 WebFlux 的特性
WebFlux 具有以下特性:
- 異步非阻塞 - 可以舉一個上傳例子。相對于 Spring MVC 是同步阻塞IO模型,Spring WebFlux這樣處理:線程發現檔案資料沒傳輸好,就先做其他事情,當檔案準備好時通知線程來處理(這裡就是輸入非阻塞方式),當接收完并寫入磁盤(該步驟也可以采用異步非阻塞方式)完畢後再通知線程來處理響應(這裡就是輸出非阻塞方式)。
- 響應式函數程式設計 - 相對于Java8 Stream 同步、阻塞的Pull模式,Spring Flux 采用Reactor Stream 異步、非阻塞Push模式。書寫采用 Java lambda 方式,接近自然語言形式且容易了解。
- 不拘束于Servlet - 可以運作在傳統的Servlet 容器(3.1+版本),還能運作在Netty、Undertow等NIO容器中。
1.2 WebFlux 的設計目标
- 适用高并發
- 高吞吐量
- 可伸縮性
二、Spring WebFlux 元件介紹
2.1 HTTPHandler
一個簡單的處理請求和響應的抽象,用來适配不同HTTP服務容器的API。
2.2 WebHandler
一個用于處理業務請求抽象接口,定義了一系列處理行為。相關核心實作類如下;
2.3 DispatcherHandler
請求處理的總控制器,實際工作是由多個可配置的元件來處理。
WebFlux是相容Spring MVC 基于@Controller,@RequestMapping等注解的程式設計開發方式的,可以做到平滑切換。
2.4 Functional Endpoints
這是一個輕量級函數程式設計模型。是基于@Controller,@RequestMapping等注解的程式設計模型的替代方案,提供一套函數式API 用于建立Router,Handler和Filter。調用處理元件如下:
簡單的RouterFuntion 路由注冊和業務處理過程:
java複制代碼@Bean
public RouterFunction<ServerResponse> initRouterFunction() {
return RouterFunctions.route()
.GET("/hello/{name}", serverRequest -> {
String name = serverRequest.pathVariable("name");
return ServerResponse.ok().bodyValue(name);
}).build();
}
請求轉發處理過程:
2.5 Reactive Stream
這是一個重要的元件,WebFlux 就是利用Reactor 來重寫了傳統Spring MVC 邏輯。其中Flux和Mono 是Reactor中兩個關鍵概念。掌握了這兩個概念才能了解WebFlux工作方式。
Flux和Mono 都實作了Reactor的Publisher接口,屬于時間釋出者,對消費者提供訂閱接口,當有事件發生的時候,Flux或者Mono會通過回調消費者的相應的方法來通知消費者相應的事件。這就是所謂的響應式程式設計模型。
Mono工作流程圖
隻會在發送出單個結果後完成。
Flux工作流程圖
發送出零個或者多個,可能無限個結果後才完成。
java複制代碼對于流式媒體類型:application/stream+json 或者 text/event-stream ,可以讓調用端獲得伺服器滾動結果。
對于非流類型:application/json WebFlux 預設JSON編碼器會将序列化的JSON 一次性重新整理到網絡,這并不意味着阻塞,因為結果Flux<?> 是以反應式方式寫入網絡的,沒有任何障礙。
三、WebFlux 工作原理
3.1 元件裝配過程
流程相關源碼解析-WebFluxAutoConfiguration
java複制代碼@Configuration
//條件裝配 隻有啟動的類型是REACTIVE時加載
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
//隻有存在 WebFluxConfigurer執行個體 時加載
@ConditionalOnClass(WebFluxConfigurer.class)
//在不存在 WebFluxConfigurationSupport執行個體時 加載
@ConditionalOnMissingBean({ WebFluxConfigurationSupport.class })
//在之後裝配
@AutoConfigureAfter({ ReactiveWebServerFactoryAutoConfiguration.class,
CodecsAutoConfiguration.class, ValidationAutoConfiguration.class })
//自動裝配順序
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
public class WebFluxAutoConfiguration {
@Configuration
@EnableConfigurationProperties({ ResourceProperties.class, WebFluxProperties.class })
//接口程式設計 在裝配WebFluxConfig 之前要先 裝配EnableWebFluxConfiguration
@Import({ EnableWebFluxConfiguration.class })
public static class WebFluxConfig implements WebFluxConfigurer {
//隐藏部分源碼
/**
* Configuration equivalent to {@code @EnableWebFlux}.
*/
}
@Configuration
public static class EnableWebFluxConfiguration
extends DelegatingWebFluxConfiguration {
//隐藏部分代碼
}
@Configuration
@ConditionalOnEnabledResourceChain
static class ResourceChainCustomizerConfiguration {
//隐藏部分代碼
}
private static class ResourceChainResourceHandlerRegistrationCustomizer
implements ResourceHandlerRegistrationCustomizer {
//隐藏部分代碼
}
WebFluxAutoConfiguration 自動裝配時先自動裝配EnableWebFluxConfiguration 而EnableWebFluxConfiguration->DelegatingWebFluxConfiguration ->WebFluxConfigurationSupport。
最終WebFluxConfigurationSupport 不僅配置DispatcherHandler 還同時配置了其他很多WebFlux核心元件包括 異常處理器WebExceptionHandler,映射處理器處理器HandlerMapping,請求擴充卡HandlerAdapter,響應處理器HandlerResultHandler 等。
DispatcherHandler 建立初始化過程如下;
java複制代碼public class WebFluxConfigurationSupport implements ApplicationContextAware {
//隐藏部分代碼
@Nullable
public final ApplicationContext getApplicationContext() {
return this.applicationContext;
}
//隐藏部分代碼
@Bean
public DispatcherHandler webHandler() {
return new DispatcherHandler();
}
java複制代碼public class DispatcherHandler implements WebHandler, ApplicationContextAware {
@Nullable
private List<HandlerMapping> handlerMappings;
@Nullable
private List<HandlerAdapter> handlerAdapters;
@Nullable
private List<HandlerResultHandler> resultHandlers;
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
initStrategies(applicationContext);
}
protected void initStrategies(ApplicationContext context) {
//注入handlerMappings
Map<String, HandlerMapping> mappingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
context, HandlerMapping.class, true, false);
ArrayList<HandlerMapping> mappings = new ArrayList<>(mappingBeans.values());
AnnotationAwareOrderComparator.sort(mappings);
this.handlerMappings = Collections.unmodifiableList(mappings);
//注入handlerAdapters
Map<String, HandlerAdapter> adapterBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
context, HandlerAdapter.class, true, false);
this.handlerAdapters = new ArrayList<>(adapterBeans.values());
AnnotationAwareOrderComparator.sort(this.handlerAdapters);
//注入resultHandlers
Map<String, HandlerResultHandler> beans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
context, HandlerResultHandler.class, true, false);
this.resultHandlers = new ArrayList<>(beans.values());
AnnotationAwareOrderComparator.sort(this.resultHandlers);
}
**流程相關源碼解析-**HTTPHandlerAutoConfiguration
上面已講解過WebFlux 核心元件裝載過程,那麼這些元件又是什麼時候注入到對應的容器上下文中的呢?其實是在重新整理容器上下文時注入進去的。
org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext#onRefresh
java複制代碼public class ReactiveWebServerApplicationContext extends GenericReactiveWebApplicationContext
implements ConfigurableWebServerApplicationContext {
@Override
protected void onRefresh() {
super.onRefresh();
try {
createWebServer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start reactive web server", ex);
}
}
private void createWebServer() {
WebServerManager serverManager = this.serverManager;
if (serverManager == null) {
String webServerFactoryBeanName = getWebServerFactoryBeanName();
ReactiveWebServerFactory webServerFactory = getWebServerFactory(webServerFactoryBeanName);
boolean lazyInit = getBeanFactory().getBeanDefinition(webServerFactoryBeanName).isLazyInit();
// 這裡建立容器管理時注入httpHandler
this.serverManager = new WebServerManager(this, webServerFactory, this::getHttpHandler, lazyInit);
getBeanFactory().registerSingleton("webServerGracefulShutdown",
new WebServerGracefulShutdownLifecycle(this.serverManager));
// 注冊一個 web容器啟動服務類,該類繼承了SmartLifecycle
getBeanFactory().registerSingleton("webServerStartStop",
new WebServerStartStopLifecycle(this.serverManager));
}
initPropertySources();
}
protected HttpHandler getHttpHandler() {
String[] beanNames = getBeanFactory().getBeanNamesForType(HttpHandler.class);
if (beanNames.length == 0) {
throw new ApplicationContextException(
"Unable to start ReactiveWebApplicationContext due to missing HttpHandler bean.");
}
if (beanNames.length > 1) {
throw new ApplicationContextException(
"Unable to start ReactiveWebApplicationContext due to multiple HttpHandler beans : "
+ StringUtils.arrayToCommaDelimitedString(beanNames));
}
//容器上下文擷取httpHandler
return getBeanFactory().getBean(beanNames[0], HttpHandler.class);
}
而這個HTTPHandler 是由HTTPHandlerAutoConfiguration裝配進去的。
java複制代碼@Configuration
@ConditionalOnClass({ DispatcherHandler.class, HttpHandler.class })
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
@ConditionalOnMissingBean(HttpHandler.class)
@AutoConfigureAfter({ WebFluxAutoConfiguration.class })
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
public class HttpHandlerAutoConfiguration {
@Configuration
public static class AnnotationConfig {
private ApplicationContext applicationContext;
public AnnotationConfig(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
//建構WebHandler
@Bean
public HttpHandler httpHandler() {
return WebHttpHandlerBuilder.applicationContext(this.applicationContext)
.build();
}
}
流程相關源碼解析-web容器
org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext#createWebServer 。在建立WebServerManager 容器管理器時會擷取對應web容器執行個體,并注入響應的HTTPHandler。
java複制代碼class WebServerManager {
private final ReactiveWebServerApplicationContext applicationContext;
private final DelayedInitializationHttpHandler handler;
private final WebServer webServer;
WebServerManager(ReactiveWebServerApplicationContext applicationContext, ReactiveWebServerFactory factory,
Supplier<HttpHandler> handlerSupplier, boolean lazyInit) {
this.applicationContext = applicationContext;
Assert.notNull(factory, "Factory must not be null");
this.handler = new DelayedInitializationHttpHandler(handlerSupplier, lazyInit);
this.webServer = factory.getWebServer(this.handler);
}
}
以Tomcat 容器為例展示建立過程,使用的是 TomcatHTTPHandlerAdapter 來連接配接Servlet 請求到HTTPHandler元件。
java複制代碼public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFactory implements ConfigurableTomcatWebServerFactory {
//隐藏部分代碼
@Override
public WebServer getWebServer(HttpHandler httpHandler) {
if (this.disableMBeanRegistry) {
Registry.disableRegistry();
}
Tomcat tomcat = new Tomcat();
File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
Connector connector = new Connector(this.protocol);
connector.setThrowOnFailure(true);
tomcat.getService().addConnector(connector);
customizeConnector(connector);
tomcat.setConnector(connector);
tomcat.getHost().setAutoDeploy(false);
configureEngine(tomcat.getEngine());
for (Connector additionalConnector : this.additionalTomcatConnectors) {
tomcat.getService().addConnector(additionalConnector);
}
TomcatHttpHandlerAdapter servlet = new TomcatHttpHandlerAdapter(httpHandler);
prepareContext(tomcat.getHost(), servlet);
return getTomcatWebServer(tomcat);
}
}
最後Spring容器加載後通過SmartLifecycle實作類WebServerStartStopLifecycle 來啟動Web容器。
WebServerStartStopLifecycle 注冊過程詳見:org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext#createWebServer
3.2 完整請求處理流程
(引用自:blog.csdn.net)
該圖給出了一個HTTP請求處理的調用鍊路。是采用Reactor Stream 方式書寫,隻有最終調用 subscirbe 才真正執行業務邏輯。基于WebFlux 開發時要避免controller 中存在阻塞邏輯。列舉下面例子可以看到Spring MVC 和Spring Webflux 之間的請求處理差別。
java複制代碼@RestControllerpublic
class TestController {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@GetMapping("sync")
public String sync() {
logger.info("sync method start");
String result = this.execute();
logger.info("sync method end");
return result;
}
@GetMapping("async/mono")
public Mono<String> asyncMono() {
logger.info("async method start");
Mono<String> result = Mono.fromSupplier(this::execute);
logger.info("async method end");
return result;
}
private String execute() {
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "hello";
}
}
日志輸出
java複制代碼2021-05-31 20:14:52.384 INFO 3508 --- [nio-8080-exec-2] c.v.internet.webflux.web.TestController : sync method start
2021-05-31 20:14:57.385 INFO 3508 --- [nio-8080-exec-2] c.v.internet.webflux.web.TestController : sync method end
2021-05-31 20:15:09.659 INFO 3508 --- [nio-8080-exec-3] c.v.internet.webflux.web.TestController : async method start
2021-05-31 20:15:09.660 INFO 3508 --- [nio-8080-exec-3] c.v.internet.webflux.web.TestController : async method end
從上面例子可以看出sync() 方法阻塞了請求,而asyncMono() 沒有阻塞請求并立刻傳回的。asyncMono() 方法具體業務邏輯 被包裹在了Mono 中Supplier中的了。當execute 處理完業務邏輯後通過回調方式響應給浏覽器。
四、存儲支援
一旦控制層使用了 Spring Webflux 則安全認證層、資料通路層都必須使用 Reactive API 才真正實作異步非阻塞。
NOSQL Database
- MongoDB (org.springframework.boot:spring-boot-starter-data-mongodb-reactive)。
- Redis(org.springframework.boot:spring-boot-starter-data-redis-reactive)。
Relational Database
- H2 (io.r2dbc:r2dbc-h2)
- MariaDB (org.mariadb:r2dbc-mariadb)
- Microsoft SQL Server (io.r2dbc:r2dbc-mssql)
- MySQL (dev.miku:r2dbc-mysql)
- jasync-sql MySQL (com.github.jasync-sql:jasync-r2dbc-mysql)
- Postgres (io.r2dbc:r2dbc-postgresql)
- Oracle (com.oracle.database.r2dbc:oracle-r2dbc)
五、總結
關于Spring MVC 和Spring WebFlux 測評很多,本文引用下做簡單說明。參考:《Spring: Blocking vs non-blocking: R2DBC vs JDBC and WebFlux vs Web MVC》。
基本依賴
java複制代碼<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-r2dbc</artifactId>
</dependency>
<!-- r2dbc 連接配接池 -->
<dependency>
<groupId >io.r2dbc</groupId>
<artifactId>r2dbc-pool</artifactId>
</dependency>
<!--r2dbc mysql 庫-->
<dependency>
<groupId>dev.miku</groupId>
<artifactId>r2dbc- mysql</artifactId>
</dependency>
<!--自動配置需要引入一個嵌入式資料庫類型對象-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<!-- 反應方程式 web 架構 webflux-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
相同資料下效果如下**;**
Spring MVC + JDBC 在低并發下表現最好,但 WebFlux + R2DBC 在高并發下每個處理請求使用的記憶體最少。
Spring WebFlux + R2DBC 在高并發下,吞吐量表現優異。