Vert.x入坑須知(1)

一直以來早有將這些年用Vert.x的經驗整理一下的想法,奈何天生不是勤快人,直到最近扶牆老師問起,遂成此文。前端

選擇理由

如今想一想,咱們應該算是國內用Vert.x的最先一批人,版本大概是1.2.x吧,當時Vert.x內置了一個比較坑爹的模塊系統,看似不錯,但其實很坑爹。但即便這樣,咱們當時仍是在技術選型上採用了它。理由大體以下:java

  • 性能,它的底層是netty,而且編程模型跟node.js一模一樣,可算得上是「node on JVM」。同時,性能評測上比node還高出很多
  • 簡單,它比netty更簡單,並且能夠輕易的支持cluster。
  • Actor模型,Verticle + Eventbus,下降了併發編程的難度。
  • WebSocket,剛好當時的項目須要這樣的方案,服務器主動向前臺推。而且Vert.x提供的EventbusBridge讓前端js的組織更好。
  • 支持Groovy,用過的都知道,這裏就不展開了。
  • 輕量級,部署簡單。

因而乎,它順利成章地成爲了咱們當時系統接入層的中流砥柱,在實踐中也確實發揮了很好的做用。node

踩坑指南

鑑於Vert.x當前的版本是3.3.3,所以本文的內容也主要針對這個版本而言,一些咱們遇到而且已經修復的bug,也就不會也沒有必要在此囉嗦了。git

此外,本文也不是入門文檔,而是爲了預防陷坑而給出的指導意見,故在閱讀本文以前還請先仔細閱讀Vert.x的文檔程序員

編程語言

雖然Vert.x的一大亮點號稱是支持「多語言」,即同一個工程內能夠同時用Java、Groovy、Javascript等不一樣語言編寫Verticle,但我仍是建議採用Java爲主,最多輔以Groovy。緣由是:我發現不少新出的Vert.x模塊仍是對Java支持最好,對於其餘的則就至關通常了,起碼不會讓你感受特地針對這個語言而開發的。加上原本Java 8以後支持lambda,Java程序員的苦逼生活其實已經改善很多。github

dgate中,我主要採用Java + Groovy的方式,二者分工也很明確:前者用於數據處理,後者則用於DSL和數據類。apache

此時,因爲混用了二者,而且可能會出現Groovy類要用到程序中Java類的狀況,那麼就要用到joint compile。在build.gradle中須要配置以下:編程

sourceSets.main.java.srcDirs = []
sourceSets.main.groovy.srcDirs += ["src/main/java"]

即,將Java類也交由Groovy編譯器來編譯。服務器

工程結構

雖然Vert.x能夠內嵌到其餘框架中,但在實際項目上我仍是偏心單獨部署,項目的構建方式則爲:gradle + fatjar。具體例子,能夠參見這個build.gradle文件併發

我在Vert.x郵件組中常常看到有新人問關於Vert.x的組織方式,其實這是沒有理解Vert.x的本質:Verticle。Verticle可視做Vert.x的一個最小部署和運行單元,簡單的說,可類比爲Servlet。所以,整個應用能夠這樣來劃分:

  • Launcher,程序入口,負責調起Vert.x的環境。
  • MainVerticle,主Verticle,負責部署程序中其餘的Verticle。
  • Verticle,程序處理邏輯,調用其餘POJO/POGO。
  • POJO/POGO,普通類,供Verticle使用。

前二者負責初始化,Verticle則相似Servlet同樣等待被觸發(來自TCP/Eventbus/HTTP的Request),在實際處理時會調用到其餘類。

這也就是爲什麼在上面的build.gradle中有這樣關鍵的兩行的緣由:

manifest {
    attributes 'Main-Class': '……'
    attributes 'Main-Verticle': '……'
}

Logging

Vert.x默認支持JUL,對於其餘Logging框架也有支持。但我嫌每次運行要敲那麼多命令很煩,那麼能夠在Launcher中強制設置環境變量:

System.setProperty("vertx.logger-delegate-factory-class-name",
                "io.vertx.core.logging.SLF4JLogDelegateFactory");

可參見dgate的Launcher代碼

部署Verticle

跟Servlet相似,多個Verticle之間也會有依賴關係,存在前後部署的須要。

對於單個Verticle之間的依賴,如A依賴B,很簡單,利用deployVerticle的回調就很好解決。由於代碼簡單,這裏就再也不單獨列出,仍是那句話,看文檔。

對於依賴多個Verticle,如A依賴B和C,則須要有點技巧了:

  • 第一也是最差的方式,就是採用callback hell方式,層層遞進。
  • 第二種方法採用rxJava,利用Observable的運算來完成。
  • 第三種方式,利用Java的Atom對象,示例代碼(Groovy)以下:
private void deployVerties(List<Map> verticles, Closure completeHandler = null) {
    AtomicInteger count = new AtomicInteger(0)
    verticles.each { verticle ->
        vertx.deployVerticle(verticle.name, verticle.option ?: [:]) { result ->
            if (result.succeeded()) {
                if (count.incrementAndGet() == verticles.size()) {
                    if (completeHandler) {
                        completeHandler.call()
                    }
                }
            } else {
                exit(verticle.name, result.cause())
            }
        }
    }
}

看到Atom對象,你是否以爲也能夠採用CountDownLatch對象?很不幸,不行。我當時作過嘗試,整個代碼立馬被Block住,直到我按了Ctrl-C。緣由在於:Block住了EventLoop。

至於deployVerticle(),它能夠接受字符串和類實例。當使用字符串時,如果非Java類,如Groovy,須要採用這樣的格式:"語言前綴:類全限定名"。如:

'groovy:hawkeyes.rtds.processor.MailMan'

此外,部署的Verticle實例並不是越多越好,還跟CPU的核數相關。

Block操做

Vert.x應用最忌諱Blocking操做,對此有多種處理:

  • 採用Worker Verticle
  • 使用executeBlocking函數

凡是涉及IO的操做,都請考慮一下。

EventBus

EventBus至關於Vert.x應用的神經系統,但有幾點須要注意:

  • 若想給部署在另外一臺機器上的Verticle發消息,這兩個Verticle必需是在一個集羣中。
  • 攔截EventBus的消息須要注意一下這個小地方

嚴格來說,3.2以後,上述第一點並不徹底正確。這兩個Verticle之間能夠採用TCP EventBusBridge來進行通訊,具體參見這篇文章

Cluster和內存計算

Cluster是當時我選擇Vert.x的一個重要考量,並且將Vert.x應用單獨打成fatjar還有一個附帶好處就是Vert.x的cli均可以直接使用,其中就包括cluster命令。

Vert.x的集羣創建在Hazelcast之上,除了集羣調度,它自己還能作內存存儲,即具有了Redis的主要功能。而且查詢語法也比Redis(2.x)的要靈活,支持類SQL語法。更重要的是,其ReadThrough特性讓人慾罷不能,簡化了編程。固然,還包括其餘如分佈式鎖、隊列、任務等等。

所謂ReadThrough,即「若內存中沒有,則查詢將下傳到下一級(一般是DB)」。Hazelcast的ReadThrough可經過實現MapLoader接口來實現。這個例子很簡單,故可查看Hazelcast的文檔瞭解。這裏重點講一下如何在Vert.x中去配置,由於Vert.x沒有對此提供直接支持。

首先,cluster.xml即爲一個標準的Hazelcast配置文件,故可在此配置相應的MapLoader便可:

<map name="map_name">
  <map-store enabled="true">
    <class-name>xxxLoader</class-name>
  </map-store>
</map>

在從未給集羣Map賦過值且第一次運行下列代碼時,注意兩個名字要相同,則觸發ReadThrough:

vertx.sharedData().getClusterWideMap("map_name") {……}

若是想在Vert.x中得到Hazelcast實例,則能夠直接使用下面代碼:

Set<HazelcastInstance> instances = Hazelcast.getAllHazelcastInstances()
hz = instances.first()

這樣即可利用Hazelcast的其餘功能。在3.3.3以後,Vert.x集羣支持Ignite,它是比Hazelcast更強大的內存計算工具。並且,在Vert.x 3.4-beta1中已經再也不是技術預覽版,往後我確定會全面擁抱它。

Ignite/Hazelcast不像Redis那樣曝光率那麼高,但鑑於其自己都是老牌內存計算軟件,且在開源以前都在高強度生產環境(沒記錯的話是銀行系統)實戰演練過,同時對比一下二者之間的功能列表,你會發現這些工具其實更強大,尤爲是Ignite。它們的文檔都不錯,值得一看。

Handler

最後說一說Handler中須要注意的地方,它很是適合寫Restful API。

以前用Vert.x寫接入層代碼,主要集中在Core、Groovy和Shell部分。此次寫dgate,算是紮紮實實用了一下Web部分。至於歷史,我就不詳細說了,總之一句話:哥是看着它長大的,;)。

Handler其實很簡單,只須要注意幾點:

  • Vert.x request Handler除了處理功能,還兼具Filter的功能。若處理完畢,請求不想讓下一個request handler處理,則直接返回便可;不然,須要調用:routingContext.next()。
  • 對於同一個URL能夠註冊多個handler,以調用順序爲準。故,想先處理的,如驗證,往前放。

至於其餘,沒啥可說的,都很簡單。

寫在最後

最後,來句雞湯:遇坑不可怕,還得敢於嘗試方能有所收穫,但願對各位有幫助!

相關文章
相關標籤/搜索