使用R2DBC實現資料庫的響應式訪問

banq發表於2018-12-08

Reactive Programming可以看作是一種程式設計模型,它透過建立事件驅動的非阻塞功能管道來促進可擴充套件性和穩定性,這些管道對資源的可用性和可處理性做出反應。延遲執行,併發和非同步性只是底層程式設計模型的結果。
只有當整個堆疊都是被動的並且所有參與的元件(應用程式程式碼,執行時容器,整合)都遵循延遲執行,非阻塞API和資料流的流式特性時,響應式程式設計的全部好處才會生效 - 基本上遵循基本假設。
雖然可以將非反應性元件引入以函式響應式編寫的應用程式,但最終結果是對可擴充套件性和穩定性影響,實際預期收益減少。在最壞的情況下,執行時行為很少或沒有區別。但是,響應式程式設計有助於提高程式碼的可讀性。
如果我們檢視響應式生態系統,我們將發現幾個框架,庫和整合。他們每個人都有自己的特長。透過通用方法或在特定響應框架的上下文中,可以很好地涵蓋許多功能領域。我們來討論關聯式資料庫整合。

最常見的問題是:我們什麼時候可以使用API​​進行響應性關聯式資料庫整合?
Java使用JDBC作為與關聯式資料庫整合的主要技術。JDBC具有阻塞性 - 沒有什麼可以減輕JDBC的阻塞性質。關於如何使呼叫無阻塞的第一個想法是將JDBC呼叫解除安裝到Executor(通常是Thread池)。雖然這種方法有些作用,但它有幾個缺點,忽略了響應式程式設計模型的好處。
響應執行時通常使用與CPU核心數匹配的有限數量的執行緒。額外的執行緒引入開銷並減少執行緒限制的影響。此外,JDBC呼叫通常會堆積在佇列中,一旦執行緒充滿請求,池將再次阻塞。所以,JDBC現在不是唯一一個選擇。

響應式資料庫API
Oracle宣佈推出ADBA,該計劃旨在透過使用期貨為Java中的非同步資料庫訪問提供標準化API。Postgres正在研究可用於第一次實驗的Postgres ADBA驅動程式PgNio是Postgres的另一個非同步驅動程式,它開始嘗試使用ADBA。
ADBA的可用性未知。它絕對不會在Java 12的版本中可使用,ADBA計劃首次亮相的Java版本目前尚不清楚。
以下程式碼段顯示了使用INSERT和SELECT語句的ADBA :

DataSource ds = dataSource();
CompletableFuture<Long> t;

try (Session session = ds.getSession()) {

  Submission<Long> submit = session
  .<Long>rowCountOperation(
    "INSERT INTO legoset (id, name, manual) " +
    "VALUES($1, $2, $3)")
    .set("$1", 42055, AdbaType.INTEGER)
    .set("$2", "Description", AdbaType.VARCHAR)
    .set("$3", null, AdbaType.INTEGER)
    .apply(Result.RowCount::getCount)
    .submit();

  t = submit.getCompletionStage().toCompletableFuture();
}

t.join();

CompletableFuture<List<Map<String, Object>>> t;
try (Session session = ds.getSession()) {

  Submission<List<Map<String, Object>>> submit = session
    .<List<Map<String, Object>>> rowOperation(
      "SELECT id, name, manual FROM legoset")
    .collect(collectToMap()) // custom collector
    .submit();
  t = submit.getCompletionStage().toCompletableFuture();
}

t.join();


請注意,這collectToMap(…)是應用程式提供的函式的示例,該函式將結果提取到所需的返回型別。

R2DBC
由於缺乏標準API和驅動程式不可用,Pivotal的團隊開始研究反應性關係API的想法,該API非常適合用於反應式程式設計。他們提出了R2DBC,它代表了Reactive Relational Database Connectivity。截至目前,R2DBC是一個孵化器專案,用於評估可行性並開始討論驅動程式供應商是否有興趣支援反應/非阻塞/非同步驅動程式。

截至目前,有三種驅動程式實現:


R2DBC附帶API規範(r2dbc-spi)和客戶端(r2dbc-client),使SPI可用於應用程式。
以下程式碼段顯示R2DBC SPI使用INSERT和SELECT語句:

ConnectionFactory connectionFactory = null;

Mono<Integer> count = Mono.from(connectionFactory.create())
  .flatMapMany(it ->
    it.createStatement(
    "INSERT INTO legoset (id, name, manual) " +
    "VALUES($1, $2, $3)")
      .bind("$1", 42055)
      .bind("$2", "Description")
      .bindNull("$3", Integer.class)
      .execute())
  .flatMap(io.r2dbc.spi.Result::getRowsUpdated)
  .next();

Flux<Map<String, Object>> rows = Mono.from(connectionFactory.create())
  .flatMapMany(it -> it.createStatement(
    "SELECT id, name, manual FROM legoset").execute())
  .flatMap(it -> it.map((row, rowMetadata) -> collectToMap(row, rowMetadata)));



雖然上面的程式碼有點笨重,但R2DBC還附帶了一個客戶端庫專案,用於更人性化的使用者API。R2DBC SPI不用於直接使用,而是透過客戶端庫使用。

使用R2DBC客戶端重寫的相同程式碼將是:

R2dbc r2dbc = new R2dbc(connectionFactory);

Flux<Integer> count = r2dbc.inTransaction(handle ->
  handle.createQuery(
    "INSERT INTO legoset (id, name, manual) " +
    "VALUES($1, $2, $3)")
    .bind("$1", 42055)
    .bind("$2", "Description")
    .bindNull("$3", Integer.class)
    .mapResult(io.r2dbc.spi.Result::getRowsUpdated));

Flux<Map<String, Object>> rows = r2dbc
  .inTransaction(handle -> handle.select(
    "SELECT id, name, manual FROM legoset")
  .mapRow((row, rowMetadata) -> collectToMap(row, rowMetadata));



請注意,collectToMap(…)作為應用程式提供的函式的示例,該函式將結果提取到所需的返回型別。

Spring Data團隊將Spring Data R2DBC作為孵化器啟動,透過資料庫客戶端提供響應API並支援反應式儲存庫。使用Spring Data R2DBC重寫的示例程式碼將是:

DatabaseClient databaseClient = DatabaseClient.create(connectionFactory);

Mono<Integer> count = databaseClient.execute()
  .sql(
    "INSERT INTO legoset (id, name, manual) " +
    "VALUES($1, $2, $3)")
  .bind("$1", 42055)
  .bind("$2", "Description")
  .bindNull("$3", Integer.class)
  .fetch()
  .rowsUpdated();

Flux<Map<String, Object>> rows = databaseClient.execute()
  .sql("SELECT id, name, manual FROM legoset")
  .fetch()
  .all();


R2DBC及其生態系統仍然很年輕,並要求進行實驗和反饋以收集用例,並檢視反應性關聯式資料庫整合是否有意義。

Fibers的JDBC
雖然JDBC和其他技術暴露了阻塞API(主要是由於等待I / O),但Project Loom引入Fibers了輕量級抽象,將阻塞API轉變為非阻塞API。一旦呼叫遇到阻塞API,就可以透過堆疊切換實現這一點。因此Fiber,繼續使用阻塞API的先前流程的基礎嘗試。
該Fiber執行模式大大減少了所需的原生執行緒數。結果是更好的可伸縮性和非阻塞行為 - 透過解除安裝對Fiber-backed的阻塞呼叫Executor。我們所需要的只是一個適當的API,允許在Fiber實現上使用非阻塞JDBC 。

結論
反應式程式設計和關聯式資料庫的未來是什麼?老實說,我不知道。如果我嘗試了一個有根據的猜測,我可以看到Project Loom和它的基於Fiber的Executor與成熟的JDBC驅動程式相結合,成為業界潛在的遊戲規則改變者。隨著Java的加速釋出節奏,這可能並不遙遠。

ADBA的目標是包含在Java標準版執行時中,根據當前的時間表,我預計它將在早於2021年的Java 17中出現。

與現在可用的R2DBC形成對比。它配備了驅動程式和客戶端,並允許實驗使用。R2DBC的一個簡潔的副作用是它暴露完全反應的API,同時獨立於底層資料庫引擎。隨著已經發布的版本,沒有必要猜測Project Loom,也不需要等待三年才能測試其驅動API。今天就可以使用R2DBC。

 

相關文章