Vert.x入坑須知(4)

最開始以爲這個系列也就最多3篇了不得了(由於事不過三嘛),沒曾想竟然迎來了第四篇!html

Kotlin

因爲最近決定投身到區塊鏈的學習當中的緣故,出於更好的理解它的基本概念,本身動手參考文章寫了一個迷你區塊鏈的例子。採用了kotlin + vertx的工具選擇。此次嘗試再次驗證了我在本系列一開篇所說:建議以Java語言開發爲主。緣由很簡單,由於這個是基礎,因此各方面支持(包括文檔和功能方面)確定是Java語言優先。前端

在作這個區塊鏈的例子時,Vertx Kotlin的文檔讓我有極爲糟糕的體驗:從整篇文檔中,你找不到一個完整的用kotlin書寫Verticle的例子,閱讀的時候就感受內容有跳躍。雖然你能夠猜出應該是繼承AbstractVerticle,但你確定仍是但願文檔中明確指出來。java

固然啦,儘管有這樣的問題,寫代碼的體驗仍是不錯的。就build.gradle而言,跟Java + Groovy組合的差異不大。惟一須要注意的是,你可能須要將kotlin的jvmTarget設置爲「1.8」。具體的配置能夠參考工程的build.gradlereact

總之,發現文檔有問題,就先查Java文檔。git

靜態資源

上面的區塊鏈的例子由兩部分組成:前端靜態頁面 + 後端的Verticle,前端靜態頁面經過Ajax請求與後端的Verticle交互。這其實就是經過Vertx-Web的StaticHandler來實現的,很簡單。這裏只提兩個須要留心的小地方。github

首先,靜態資源的根路徑默認狀況下是:src/main/resources/webroot。即當你請求「http://localhost:8080/index.html」時,其實對應的是:src/main/resources/webroot/index.html。web

其次,目前的Web應用的URL不多會直接出現「……/xxx.html」。按照向Spring MVC或Grails這裏框架的作法,通常是通過一個action,而後將瀏覽器導向某個頁面。在作這個例子時,其實沒有這麼複雜的邏輯,讓瀏覽器直接去加載某個頁面(如configure.html)就能夠了。但這樣會出現一個讓人很不爽的Path:「/configure.html」,而其餘的路徑由於主要是負責處理Ajax請求,都是形如「/mine」這樣的路徑。sql

爲了統一路徑風格,這裏採用了一個小技巧:RoutingContext.reroute。參考代碼以下:數據庫

router.get("/configure").handler({ rc: RoutingContext -> rc.reroute("/configure.html") })

JDBC鏈接池

Vert.x JDBC client缺省的鏈接池提供者是c3p0,但它也支持其餘其餘的鏈接池,好比大名鼎鼎的Hikari。但遺憾的是,文檔中沒有給出一個完整的代碼示例。對於想換用其餘鏈接池的同窗,能夠參考下面的代碼:編程

JDBCClient.createShared(vertx, new JsonObject()
                .put('provider_class', 'io.vertx.ext.jdbc.spi.impl.HikariCPDataSourceProvider')
                .put('driverClassName', 'org.postgresql.Driver')
                .put('jdbcUrl', jdbcUrl)
                .put('username', username)
                .put('password', password)
                .put('maximumPoolSize', maximumPoolSize)
                .put('minimumIdle', minimumIdle)
                .put('cachePrepStmts', true)
                .put('prepStmtCacheSize', 250)
                .put('prepStmtCacheSqlLimit', 2048));

對於用Postgresql的同窗,還能夠看看Reactive Postgres Client,一個高性能的輕量級jdbc client同時自帶鏈接池,做者也是vertx的貢獻者。

回調的線程安全性

使用Vert.x的最大好處就是極大簡化了多線程編程的複雜性,大部分時候你幾乎不須要去操心,這部份內容分別在文檔的Standard verticlesWorker verticles有描述。

但文檔中並無專門闡述這一原則是否對於回調函數也適用,畢竟回調函數執行的時機不肯定而且典型的Vert.x程序充斥着回調。對於這個問題,簡單地說:一樣適用。下面的示例代碼能夠驗證這一點:

public class Vert1 extends AbstractVerticle {

    long count = 0;

    @Override
    public void start() {
        HttpClient httpClient = vertx.createHttpClient();

        for (int i = 0; i < 20; i++) {
            httpClient.getAbs("http://www.baidu.com/", response -> {
                count++;
                System.out.println(count);
            }).end();
        }
    }

}

從輸出來看,徹底正確。做爲對比,你能夠在groovyConsole中運行下面的代碼(多按幾回ctrl - r):

int count = 0
def c = { 
    10.times { 
        count++
        println count }
}
def t1 = new Thread(c)
def t2 = new Thread(c)
t1.start()
t2.start()

而且,經過調研Vert.x源代碼,你能夠(在HttpClientRequestBase中)發現:

void handleResponse(HttpClientResponseImpl resp) {
    synchronized (getLock()) {
      // If an exception occurred (e.g. a timeout fired) we won't receive the response.
      if (exceptionOccurred == null) {
        long timeoutMS = currentTimeoutMs;
        cancelOutstandingTimeoutTimer();
        try {
          doHandleResponse(resp, timeoutMS);
        } catch (Throwable t) {
          handleException(t);
        }
      }
    }
  }

很明顯,Vert.x內部已經爲你提早預防了,這就是框架的力量!若是你還在用Netty,不妨考慮Vert.x這個建構於它之上的高層工具吧。

Unmount Subrouters

Subrouter是個好東東,可API設計有個問題:只有mount,沒有unmount!通常狀況下,unmount的確用不上,但你一旦想實現動態路由時,它就是萬萬不可缺乏的了。

好在我本身摸索出了下面的方法:

public static void unMountSubRouter(Router router, String root) {
    router.getRoutes().stream()
            .filter(route -> route.getPath() != null && route.getPath().startsWith(root))
            .forEach(route -> route.remove());
}

有趣的是,Vert.x的開發者曾經以爲subrouter用處不大,並動了把它在將來拿掉的念頭。當這個想法被提出來徵求社區意見時,立馬有人跳出來講:「subrouter的設計很是好,哥的程序嚴重依賴它,請繼續保留。」

應用架構

我曾經不止一次看到初學者在問相似這樣的問題:

  • 應該建立多少Verticle實例?
  • Vert.x的應用該怎麼去設計?
  • 怎麼跟現有的框架結合?
  • ……

要回答這些問題,須要首先搞清楚幾個事實。

  1. 不要將Verticle和Thread混爲一談,它們不是一類東西。簡單的說,能夠這樣理解:Verticle由Thread來執行。
  2. Vert.x中的Verticle由種類之分,不一樣類型的Verticle適用於不一樣場景,這一點在文檔中已經有詳細的闡述。
  3. Vert.x自己是一個庫,並不妨礙它跟其餘框架(如Grails)的結合。只不過就我我的而言,更偏好將Vert.x應用單獨使用。主要是避免引入太多的複雜性,而且出於靈活部署的須要。

而且,經過觀察其餘人寫的Vert.x代碼(包括Vert.x本身的那些子項目),能夠總結出來幾個套路。

Master - Worker

這是最多見的結構:

  • 標準Verticle,負責接收外部請求,完成請求分派和結果收集
  • Worker Verticle,負責髒活累活

標準Verticle和Worker Verticle之間經過eventbus進行交互,整個架構其實也很簡單:

request <---> standard verticles <---> worker verticle

這裏的一個典型反模式,尤爲是初學者會大機率犯的錯誤:將本該worker乾的活,交給了標準verticle,即將圖中後兩個組件合二爲一。這種狀況在寫Vertx Web時很是容易出現,尤爲受傳統MVC框架的影響,無心識地將原來的編程套路給照搬過來了:在Handler中進行了大量操做。我本身也不例外,走過這段彎路。

Don't block me!

以Vert.x Web應用爲例,因爲Handler其實是在eventloop上執行,若它被阻塞,即致使後續請求所有沒法獲得處理。所以,最合適的作法就是:

  • 對於簡單業務,採用異步庫。
  • 對於複雜業務,乾脆交給worker去處理。

異步工具庫

利用Vert.x的特色,將IO操做封裝成異步庫。

微服務

用Vert.x將業務功能封裝成微服務,而後利用現成的基礎設施與其餘應用交互:

  • 利用kafka隊列實現服務間的交互
  • 利用TCP Bridge實現與tcp client的交互
  • 利用Eventbus Bridge實現與頁面的交互
  • 利用數據庫實現與應用的簡單交互
  • ……

這也是我最喜歡用的模式,輕量,簡單,部署方便。我不太喜歡在一個原本就已經含有複雜業務邏輯的Grails應用中再包含一個Vert.x Verticle了。

或許有同窗對於上面的最後一項,感到疑惑。其實這個很簡單,以Postgresql爲例,能夠採用兩種模式:

  • 簡單的「定時任務+表」的方式,經過表的某個字段實現服務間的集成
  • 利用PG自身支持的pub/sub功能

行了,本篇寫到這裏也差很少了。最後給你們推薦一個網頁:Awesome Vert.x,上面有很多不錯的資源。


本系列其餘文章:

相關文章
相關標籤/搜索