為何現在響應式程式設計在業務開發微服務開發不普及

乾貨滿滿張雜湊發表於2021-02-08

為何現在響應式程式設計在業務開發微服務開發不普及

主要因為資料庫 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 客戶端,但是都不普及:

  1. Vert.x 客戶端:https://vertx.io/docs/vertx-jdbc-client/java/
  2. r2jdbc 客戶端:http://r2dbc.io/
  3. Jasync-sql 客戶端:https://github.com/jasync-sql/jasync-sql

每日一刷,輕鬆提升技術,斬獲各種offer:

image

相關文章