Vert.x入坑須知(2)

當初建立簡書帳號的時候曾立下宏願,但願保持周更,無奈現實殘酷,整個5月都處於忙忙碌碌的狀態,竟然令這個原本並不算太宏偉的目標難覺得繼,最終致使5月份交了白卷!【好吧,我認可,是我意志不夠堅決,太懶了,;)】java

最終的負罪感致使了本文的誕生,同時也意味着一個新坑的開啓。做爲Vert.x的使用者,我會按期更新得到的經驗,但願爲同好提供一些小小的幫助,掃除前進的障礙。同時,做爲一個懶人和見異思遷者,事先聲明,我沒有辦法保證個人興趣不會發生轉移,因此,我也不知道這個坑能開多久,望你們注意。git

那麼,讓咱們進入主題。github

HTTPS請求

這是在跟某運營商的NB-IOT平臺對接時解決的問題,其實最終的整個代碼並不複雜,相似下面(採用雙向認證方式):web

httpClient = vertx.createHttpClient(new HttpClientOptions()
        .setSsl(true)
        .setPfxKeyCertOptions(new PfxOptions().setPath("xxx.pkcs12").setPassword("xxx"))
        .setTrustStoreOptions(new JksOptions().setPath("xx.jks").setPassword("xxx"))
        .setVerifyHost(false));

基本上跟文檔上的例子差很少,值得注意的地方就是最後一行代碼:setVerifyHost(false),當時這個運營商的平臺也處於測試狀態,並無域名,是自簽證書,設置爲false,客戶端不驗證服務端證書的合法性,則可規避這一點,讓整個開發能夠不受影響繼續下去。後端

JWT的密鑰

隨着微服務的流行,JWT也變得流行起來,但有一點開發常常忽視的是直接將生成JWT要用到的證書包含在源代碼中。這一點實際上是很是不妥的,有潛在的安全問題。緩存

一個常識是:開發測試用的證書和產品環境的證書要分開,由不一樣的人來負責管理!安全

好比,在dgate中,產品環境的證書由運維經過下面的環境變量指定(例子):服務器

export dgate_key_store=./test1.jceks
export dgate_key_type=jceks
export dgate_key_password=test123

在生成JWT對象時,從環境變量裏直接拿這些值,供將來使用。網絡

HTTP Form/Upload請求

這是在實現dgate遇到的需求,小夥伴們強烈要求支持Form提交和文件上傳,因而就有了RelayHandler的誕生。它的實際做用就是請求的透傳,即將請求內容原封不動的搬到後端服務那裏去。運維

實現很簡單,看代碼就知道,但在編寫測試代碼時就遇到了須要發起Form和Upload的問題。這個需求要是放在如今很簡單,由於Vert.x從3.4開始提供了WebClient模塊,用它能夠很輕易的實現這樣的需求。

可在當時,這個模塊尚未出來咧!那就只能本身動手了,由於並無那麼麻煩,本質上就是按照HTTP協議的要求,向Request寫入對應格式的內容就好了。這一點能夠從RequestUtils的form和upload方法實現裏看出來。

UDP的Socket

這是在另外一個項目中遇到趣事,其實既不是需求也不是問題,而是錦上添花,方便調試。在這個項目中,咱們用Vert.x實現了一個UDP Server,用戶的設備會直接向這個Server發包。在必定的條件下,如時鐘不對,Server會向設備發起一個校時命令。

做爲負責任的開發,咱們固然不會依賴用戶的硬件設備來進行調試啦!也就是說,咱們本身是實現了一個MockClient的,它徹底實現了設備和服務器的通訊協議。內測沒問題以後就到了聯調階段了,可恰恰咱們已經發出了校時命令(終端有輸出),但硬件卻說沒有收到!

要命的是,經過Linux的tcpdump來抓包,竟然在UDP Server的地址和端口上沒有抓到對應的包!這真是渾身是嘴也說不清啊!並且,本身寫的Mock與Server老是可以順暢的通訊!

遇到這麼個怪問題那就不得不上網絡調試的終極大殺器Wireshark了,看看到底發生了什麼。很快,問題定位出來了,下發命令確實已經發出,只不過是從另外一個端口發出去的。這是由於寫代碼時,以爲UDP反正就沒有鏈接的概念,只要有對方的地址信息那就直接發送過去就行了。因而,在發送前從新建立了一個DatagramSocket實例,經過它直接發出去了。

實際的修改也很簡單,用UDP Server對應的那個DatagramSocket實例發送就行了。最後的結果固然是皆大歡喜。

與Ignite的集成

我以前不止一次的表達出對於Ignite的喜好,以爲它比Redis要好(注:最近有所改變,由於Redis提供了對於HpyerLogLog開箱即用的支持,而Ignite則貌似沒有。)。此次,在項目中終於採用了以它爲基礎的Vert.x集羣方案

整個集成和使用並不複雜,這是咱們作過的事情:

  1. 引入依賴和ignite.xml
  2. 在Launcher中強制設置爲Cluster模式,簡化命令行啓動:
@Override
public void beforeStartingVertx(VertxOptions options) {
    //Force to use cluster mode
    options.setClustered(true);
}
  1. 爲了方便後續得到配置裏的緩存,須要對ignite在啓動時進行初始化,因而咱們寫了一個工具類,其主要用處就是在啓動前從Vert.x中得到Ignite並保存起來,方便後續按名字獲取緩存時直接使用。初始化代碼以下(一樣是在Laucher裏面,但因爲要得到Vertx實例,故放在afterStartingVertx裏):
if (ignite == null) {
    vertxInstance = vertx;
    ClusterManager clusterManager = ((VertxInternal) vertx).getClusterManager();
    String uuid = clusterManager.getNodeID();
    ignite = Ignition.ignite(UUID.fromString(uuid));
    ...
}

有了Ignite實例以後,其餘的功能,如Read/Write Through、Cache事件等等,就是按照Ignite文檔的指示來作了。這部分的內容已經屬於Ignite的領域且文檔也有詳細的闡述,這裏就再也不贅述。

壓力測試

做爲服務器應用,怎麼能不來一把壓測來對其性能摸底呢?在實際過程當中,新手最容易忘記的問題就是:沒有修改操做系統的最大可用句柄數,致使壓力還沒上來就進行不下去了。

除此以外,用Vert.x自寫壓測代碼時也須要注意:

  1. 對於UDP服務器來說,注意設置足夠大小的接收緩存,不然會致使Client的包被丟棄,且沒有任何錯誤提示。表象就彷彿服務器掛了或沒有能力處理同樣,而實際的壓力其實遠遠沒有達到服務器的極限。參見下面的代碼:
vertx.createDatagramSocket(
                new DatagramSocketOptions()
                        .setReceiveBufferSize(UDP_RECEIVE_BUFFER_SIZE)
                        .setSendBufferSize(UDP_SEND_BUFFER_SIZE))
                .listen(..., "0.0.0.0", asyncResult -> { ... }
  1. 在壓測發起端,避免爲每一個Client生成一個長時間存在的週期Timer。相反,使用一個週期Timer,但針對每一個Client生成一個一次性的Timer,模擬隨機發送。同時,要記得用完以後當即釋放。相似的結構以下:
vertx.setPeriodic(20000, tid -> {
    mockClients.forEach((uid, socket) -> {
        vertx.setTimer(ThreadLocalRandom.current().nextInt(10, 10000)
                , id -> {
                    logger.info("...", uid);
                    send(...);
                    vertx.cancelTimer(id);
                });
    });
});

自此,這段時間積壓下來,以爲有必要寫寫的內容已經所有傾倒完畢,敬請期待下一期(若是真有的話,;))。

相關文章
相關標籤/搜索