使用vertx構建響應式微服務-第二章 理解響應式微服務和Vert.x

微服務不是一個新事物,1970年就出現了,如今右變得流行。因爲它可以快速構建或修改出有價值的產品。

我們都知道分佈式系統很複雜很難構建,於是響應式應運而生。

但是什麼是響應式(reactive)呢?牛津詞典解釋是“對刺激的反應”.

所以響應式系統根據外界變化調整自己的表現。(例如bootstrap根據屏幕的大小改變佈局)

  • 響應式編程-數據驅動
  • 響應式系統-異步消息 


讓我們看看RxJava(ReactiveXJava)是如何實現的。

observable.subscribe(   data -> { // onNext
    System.out.println(data);
  },
  error -> { // onError     error.printStackTrace();
  },
  () -> { // onComplete
    System.out.println("No more data");
  }
);

RxJava可以做什麼,下邊是一個上傳下載的示例

// Asynchronous task downloading a document
Future<String> downloadTask = download(); // Create a single completed when the document is downloaded.
Single.from(downloadTask)
  // Process the content
  .map(content -> process(content))
  // Upload the document, this asynchronous operation   // just indicates its successful completion or failure.   .flatMapCompletable(payload -> upload(payload))
  .subscribe(
  () -> System.out.println("Document downloaded, updated
               and uploaded"),   t -> t.printStackTrace() );

自適應系統有兩個特性:

  • 彈性-水平伸縮的能力(根據訪問量增加減少實例)
  • 回調-處理失敗的能力

當自適應系統面臨負載峯值時,他會自動增加一個實例。

下一章我們將會看到Vert.x如果應對這些問題。

響應式微服務

我們構建一個微服務系統,每個服務器都可能出現各種問題,但是我們不能讓它影響到整個系統。你的程序必須接受改變和有能力處理錯誤。

無源微服務是自治的。 他們可以適應周圍服務的可用性或不可用性。 然而,自治與隔離配合。 活性微服務可以在本地處理失敗,獨立行動,並根據需要與其他人合作。 反應式微服務使用異步消息傳遞與其對等方進行交互。 它還接收消息並能夠產生對這些消息的響應。

由於異步消息傳遞,反應式微服務可能會面臨失敗並相應地調整其行爲。 不應該傳播失敗,而應該接近根本原因。 當微服務爆炸時,消費者微服務必須處理失敗並且不傳播它。 這種隔離原理是防止故障冒泡並破壞整個系統的關鍵特徵。 恢復力不僅關乎管理失敗,還關係到自我修復。 發生故障時,被動微服務應實施恢復或補償策略。

最後,反應式微服務必須具有彈性,因此係統可以根據實例的數量來管理負載。 這意味着一系列限制,例如避免內存中狀態,如果需要可以在實例之間共享狀態,或者能夠將消息路由到相同的實例以進行有狀態服務。

什麼是Vert.x

Vert.x是一個工具集,它可以用非阻塞的異步模型來開發響應式和分佈式的系統。因爲它是一個工具集不是一個框架,所以你可以像使用其他lib一樣把它放到你的項目中。它不強制你如何構造或組織你的系統,你可以用任何方式使用它。它很靈活,你可以用它構建一個獨立的應用,也可以把它嵌入到其他大型系統中。

站在開發者的角度,Vert.x是一套JAR文件。每一個Vert.x模塊都是一個JAR文件你可以添加到你的CLASSPATH中。從HTTP服務到HTTP客戶端到消息通信到更低層的協議例如TCP和UDP,Vert.x都提供了一整套模組來讓你構建你想構建的應用。你可以選擇任何模組作爲Vert.x核心的插件來構建你的系統。下圖抽象出一個Vert.x的生態系統。

Vert.x也提供了很多原料幫組你構建微服務系統。同時它也可以構建響應式微服務。

異步開發模式

所有用Vert.x構建的應用都是異步的。Vert.x應用是事件驅動和非阻塞性的。當關注的事情發生時你的應用會發生通知。讓我們來看一個具體的示例。Vert.x提供了一個簡單的方式來創建一個HTTP服務。每當收到HTTP請求這個HTTP服務都會接到通知。

vertx.createHttpServer()
    .requestHandler(request -> {
        // This handler will be called every time an HTTP
        // request is received at the server         request.response().end("hello Vert.x");
    })
    .listen(8080);

在例子中,我們設置了一個請求處理來接收HTTP請求(事件)和返回“hello Vert.x”(響應).

一個Handler是一個事件通知可以調用的方法。在示例中,這個方法被調用每當收到請求。需要注意的是這個方法沒有返回結果。然後一個Handler可以提供一個結果。

除了很少的例外,沒有API可以阻塞調用進程。如果一個結果可以立馬被提供,它也將立馬返回。否者,一個Handler將會過一會纔會收到。Handler會被通知,當一個事件準備好被處理或者一個異步處理已經計算出了結果。

在傳統編程中,你可能會這樣寫代碼:

int res = compute(1,2);

在這個代碼中你等待這個方法的結果。

當你切換到異步非阻塞開發模式,你可以在返回結果後調用Handler.

compute(1, 2, res -> {
    // Called with the result
});

這就像ajax那樣。

在這個代碼片段中,compute方法不返回任何結果,所以你不必等待,結果被計算出來後會自動調用Handler.

非常感謝異步開發模式,你可以處理高併發用很少的線程。在大多數情況,Vert.x調用你的Handler用一個叫做even loop的線程。如下圖所示。它消費事件隊列,並把事件發送到對應的Handler中進行處理。

線程模式的提出有一個很大的好處,簡化了併發。因爲只有一個線程,所以你每次只用調用一個線程而不用併發。同時,也有一個非常重要的規則你需要遵守:

      不要阻塞event loop

              --Vert.x 黃金準則


因爲不會被阻塞,event loop可以在很短時間裏轉發大量事件。這被稱作反應堆模式。

讓我們想象一下,在一瞬間,你破壞了規則,在前邊的代碼片段中,Handler會一直被event loop調用。其他請求將會排隊,等待這個Handler執行完成。這樣做你將失去使用Vert.x帶來的好處。

那麼什麼可以阻塞呢?

一個顯然的例子是JDBC數據庫連接。他們天生是阻塞的。長時間的計算也是阻塞的。舉個例子,一個計算小數後點20萬位π值的代碼也是阻塞型的。不用擔心,Vert.x也提供方法來處理這些阻塞型代碼。

如下圖,Vert.x提供了很多event loop

事件被不同的event loop 調度。然而,當一個Handler被某個event loop執行,它將會一直被這個event loop執行。

多個event loop可以被均衡到不同的CPU內核上。HTTP怎麼工作呢?Vert.x只註冊一次socket監聽,然後把請求調度到不同的event loop上。

Verticles-基本代碼塊

Vert.x給你很大自由,如何實現你的應用和代碼。但是他也提供了簡單開始編寫Vert.x的方法。Verticles是一大塊可以被部署和運行的代碼。一個應用,例如一個微服務,可能包含大量verticle 同時運行在同一個Vert.x實例中。

一個典型的verticle可以創建服務端或者客戶端,註冊一套Handles,或者包含一些業務邏輯。

常見的verticle在event loop上執行並且永遠不會阻塞。Vert.x 確保verticle一直被同一個進程執行並且不會被併發,由此來避免同時構造。在java中,一個verticle是一個AbstractVerticle類的擴展

import io.vertx.core.AbstractVerticle;
public class MyVerticle extends AbstractVerticle {
    @Override
    public void start() throws Exception {
        // Executed when the verticle is deployed
    }
    @Override
    public void stop() throws Exception {
        // Executed when the verticle is un-deployed
    }
}

verticles可以創建服務端客戶端還可以調用其他verticles,也能部署其他verticles,配置他們,設置創建實例的數量。這些實例會被關聯到不同的event loop上,同時vert.x平衡這些實例的負載。

從回調到觀察

正如前邊看到的,Vert.x開發模式使用回調。在編排多個異步操作時,這種基於回調的開發模型傾向於生成複雜的代碼。

例如,讓我們看看我們將如何從數據庫中檢索數據。 首先,我們需要一個到數據庫的連接,然後我們發送一個查詢到數據庫,處理結果並釋放連接。 所有這些操作都是異步的。 使用回調函數,您可以使用Vert.x JDBC客戶端編寫以下代碼:

client.getConnection(conn -> {
    if (conn.failed()) {/* failure handling */}
    else {
        SQLConnection connection = conn.result();         connection.query("SELECT * from PRODUCTS", rs -> {             
if (rs.failed()) {/* failure handling */}
            else {
                List<JsonArray> lines =                     rs.result().getResults();                 
for (JsonArray l : lines) {
                    System.out.println(new Product(l));
                }
                connection.close(done -> {
                    if (done.failed()) {/* failure handling */}
                });
            }
        });
    }
});

雖然仍然易於管理,但該示例顯示回調可能會很快導致無法讀取的代碼。 您也可以使用Vert.x Futures處理異步操作。 與Java期貨不同,Vert.x期貨不受阻礙。 期貨提供更高層次的組合操作員來構建操作序列或並行執行操作。 通常情況下,如下面的代碼片段所示,我們構建未來以構建異步操作序列:

Future<SQLConnection> future = getConnection();
future
    .compose(conn -> {         connection.set(conn);
        // Return a future of ResultSet         
      return selectProduct(conn);
    })
    // Return a collection of products by mapping
    // each row to a Product
    .map(result -> toProducts(result.getResults()))
    .setHandler(ar -> {
        if (ar.failed()) { /* failure handling */ }
        else {
            ar.result().forEach(System.out::println);
        }
        connection.get().close(done -> {
            if (done.failed()) { /* failure handling */ }
        });
    });

然而,雖然期貨使代碼更具說明性,但我們正在檢索一批中的所有行並處理它們。 這個結果可能很大,需要很多時間來檢索。 同時,您不需要整個結果就可以開始處理它。 我們可以一行一行地處理每一行,只要你有它們。 幸運的是,Vert.x爲這種開發模式挑戰提供了答案,併爲您提供了一種使用響應式編程開發模型實現響應式微服務的方法。 Vert.x提供RxJava API來:

•組合和協調異步任務

•將輸入消息作爲輸入流進行響應

Let’s rewrite the previous code using theRxJava APIs:


// We retrieve a connection and cache it, // so we can retrieve the value later. 
Single<SQLConnection> connection = client.rxGetConnection();
 connection.flatMapObservable(conn ->
    conn
      // Execute the query
      .rxQueryStream("SELECT * from PRODUCTS")
      // Publish the rows one by one in a new Observable
      .flatMapObservable(SQLRowStream::toObservable)
      // Don't forget to close the connection
      .doAfterTerminate(conn::close)
  )
  // Map every row to a Product
  .map(Product::new)
  // Display the result one by one
  .subscribe(System.out::println);
除了提高可讀性以外,響應式編程允許您在可用時立即訂閱結果流和處理項。 藉助Vert.x,您可以選擇您喜歡的開發模式。 在這份報告中,我們將使用回調和RxJava。

我們開始編程吧!

是時候讓你的手變髒了(落灰好久了,我的鍵盤早已飢渴難耐)。我們來用maven來開發我們的第一個Vert.x應用。當然你還可以用其他工具,例如Gradle,Ant.

鍵入命令:

mkdir my-first-vertx-app 
cd my-first-vertx-app
mvn io.fabric8:vertx-maven-plugin:1.0.5:setup \
  -DprojectGroupId=io.vertx.sample \
  -DprojectArtifactId=my-first-vertx-app \
  -Dverticle=io.vertx.sample.MyFirstVerticle

這個命令會自動生成vert.xmaven項目。

編寫你的第一個verticle

修改MyFirstVerticle.java

package io.vertx.sample; import io.vertx.core.AbstractVerticle;
/**  * A verticle extends the AbstractVerticle class.
 */
public class MyFirstVerticle extends AbstractVerticle {
  @Override
  public void start() throws Exception {     // We create a HTTP server object     vertx.createHttpServer()
      // The requestHandler is called for each incoming
      // HTTP request, we print the name of the thread
      .requestHandler(req -> {         req.response().end("Hello from "
          + Thread.currentThread().getName());
      })
      .listen(8080); // start the server on port 8080
  }
}
mvn compile vertx:run

訪問地址localhost:8080

當你運行了vertx:run每當你修改了代碼,程序都會自動發佈,你刷新瀏覽器就可以看大。

當前請求被eventloop 0處理,你可以打開新的頁面,都是同一個event loop在處理。

使用RxJava

在pom文件中加入
<dependency>
  <groupId>io.vertx</groupId>
  <artifactId>vertx-rx-java</artifactId>
</dependency>

修改pom


創建MyFirstRXverticle

package io.vertx.sample;
// We use the .rxjava. package containing the RX-ified APIs
import io.vertx.rxjava.core.AbstractVerticle; import io.vertx.rxjava.core.http.HttpServer; public class MyFirstRXVerticle extends AbstractVerticle {
  @Override
  public void start() {
    HttpServer server = vertx.createHttpServer();     
    // We get the stream of request as Observable     
    server.requestStream().toObservable()
      .subscribe(req ->
        // for each HTTP request, this method is called
        req.response().end("Hello from "
          + Thread.currentThread().getName())
      );
    // We start the server using rxListen returning a
    // Single of HTTP server. We need to subscribe to
    // trigger the operation
    server
      .rxListen(8090)
      .subscribe();
  }
}

把你的應用打包成Fat Jar

Vert.x Maven插件 支持把你的應用打包爲fat jar.這樣你可以很容易用命令

java -jar name.jar 來運行你的應用。

mvn clean package
cd target
java -jar my-first-vertx-app-1.0-SNAPSHOT.jar

這樣你的應用又可以運行了。

fat jar自帶依賴jar運行起來很方便。

日誌,警告和其他生產要素

打包成fat jar 是一個很好的打包模式對微服務來講,因爲很方便部署和啓動。但是部署到生產環境我們還需要做什麼呢?一般來說我們還需要記錄日誌,加載外部配置,運行狀態監控等等。

不用擔心Vert.x提供了所有這些功能。你可以用Log4等來記錄日誌,可以用JMX來監控Vert.x運行。

總結

在這一章我們學習了響應式微服務和Vert.x.你同時也創建了你的第一個Vert.x應用。
本章絕非全面的指南, 只是簡要介紹了主要概念。
如果您想進一步瞭解這些主題, 請查看以下資源:

•   Reactiveprogramming vs. Reactive systems

•   The Reactive Manifesto

•   RxJava website

•   ReactiveProgramming with RxJava

•   The Vert.xwebsite



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章