為何現在響應式編程在業務開發微服務開發不普及
主要因為數據庫 IO,不是 NIO。
不論是Java自帶的Future框架,還是 Spring WebFlux,還是 Vert.x,他們都是一種非阻塞的基於Ractor模型的框架(后兩個框架都是利用netty實現)。
在阻塞編程模式里,任何一個請求,都需要一個線程去處理,如果io阻塞了,那么這個線程也會阻塞在那。但是在非阻塞編程里面,基於響應式的編程,線程不會被阻塞,還可以處理其他請求。舉一個簡單例子:假設只有一個線程池,請求來的時候,線程池處理,需要讀取數據庫 IO,這個 IO 是 NIO 非阻塞 IO,那么就將請求數據寫入數據庫連接,直接返回。之后數據庫返回數據,這個鏈接的 Selector 會有 Read 事件准備就緒,這時候,再通過這個線程池去讀取數據處理(相當於回調),這時候用的線程和之前不一定是同一個線程。這樣的話,線程就不用等待數據庫返回,而是直接處理其他請求。這樣情況下,即使某個業務 SQL 的執行時間長,也不會影響其他業務的執行。
但是,這一切的基礎,是 IO 必須是非阻塞 IO,也就是 NIO(或者 AIO)。官方JDBC沒有 NIO,只有 BIO 實現。這樣無法讓線程將請求寫入鏈接之后直接返回,必須等待響應。但是也就解決方案,就是通過其他線程池,專門處理數據庫請求並等待返回進行回調,也就是業務線程池 A 將數據庫 BIO 請求交給線程池B處理,讀取完數據之后,再交給 A 執行剩下的業務邏輯。這樣A也不用阻塞,可以處理其他請求。但是,這樣還是有因為某個業務 SQL 的執行時間長,導致B所有線程被阻塞住隊列也滿了從而A的請求也被阻塞的情況,這是不完美的實現。真正完美的,需要 JDBC 實現 NIO。
Java 自帶的 Future 框架可以這么用JDBC:
@GetMapping
public DeferredResult<Result> get() {
DeferredResult<Result> deferredResult = new DeferredResult<>();
CompletableFuture.supplyAsync(() -> {
return 阻塞數據庫IO;
//dbThreadPool用來處理阻塞的數據庫IO
}, dbThreadPool).thenComposeAsync(result -> {
//spring 的 DeferredResult 來實現異步回調寫入結果返回
deferredResult.setResult(result);
});
return deferredResult;
}
WebFlux 也可以使用阻塞JDBC,但是同理:
@GetMapping
public Mono<Result> get() {
return Mono.fromFuture(CompletableFuture.supplyAsync(() -> {
return 阻塞數據庫IO;
//dbThreadPool用來處理阻塞的數據庫IO
}, dbThreadPool));
}
Vert.x 也可以使用阻塞的JDBC,也是同理:
@GetMapping
public DeferredResult<Result> get() {
DeferredResult<Result> deferredResult = new DeferredResult<>();
getResultFromDB().setHandler(asyncResult -> {
if (asyncResult.succeeded()) {
deferredResult.setResult(asyncResult.result());
} else {
deferredResult.setErrorResult(asyncResult.cause());
}
});
return deferredResult;
}
private WorkerExecutor dbThreadPool = vertx.createSharedWorkerExecutor("DB", 16);
private Future<Result> getResultFromDB() {
Future<Result> result = Future.future();
dbThreadPool.executeBlocking(future -> {
return 阻塞數據庫IO;
}, false, asyncResult -> {
if (asyncResult.succeeded()) {
result.complete(asyncResult.result());
} else {
result.fail(asyncResult.cause());
}
});
return result;
}
相當於通過另外的線程池(當然也可以通過原有線程池,反正就是要用和請求不一樣的線程,才能實現回調,而不是當次就阻塞等待),封裝了阻塞 JDBC IO。
但是,這樣幾乎對數據庫IO主導的應用性能沒有提升,還增加了線程切換,得不償失。所以,需要使用真正實現了 NIO 的數據庫客戶端。目前有這些 NIO 的 JDBC 客戶端,但是都不普及:
- Vert.x 客戶端:https://vertx.io/docs/vertx-jdbc-client/java/
- r2jdbc 客戶端:http://r2dbc.io/
- Jasync-sql 客戶端:https://github.com/jasync-sql/jasync-sql
每日一刷,輕松提升技術,斬獲各種offer: