【Vert.x初體驗1】4行代碼實現HTTP Server

4行代碼

上古時代的Java程序員如果想寫一個HTTP服務,需要按下面的步驟操作:

  • 編寫Servlet, 實現doXXX()方法
  • 打成war包
  • 部署Tomcat
  • 將war包copy到指定目錄下進行"部署"
  • 訪問8080

一頓操作猛如虎,旁邊的小弟小妹對你佩服的五體投地。

我可能只是想說一句Hello World而已,需要這麼裝X麼?

到了中古時代,事情變的更麻煩了。雖然不需要直接寫servlet, 但爲了正確配置好"春天"這個框架,我們要寫一籮筐XML文件,一不小心寫錯Tomcat就會一堆Error。由此就誕生了"面向XML編程"的段子。

到了近代,我們終於可以直接在main()方法裏寫代碼了,但是之前還需要引入一堆xxx-starter和spring boot的打包插件,這樣才能做到打出的jar包可以執行運行。不過本質還是Servlet沒變,只是框架幫你隱藏了而已,感覺還是少了那麼點意思。

來到當下,Vert.x來拯救我們了!來看看一個最簡單的HTTP Server長啥樣:

Vertx vertx = Vertx.vertx(); // (1)
vertx.createHttpServer() // (2)
      .requestHandler(req -> req.response().end("it works!")) // (3)
      .listen(8080); // (4)

只需要4行代碼!而且,你不需要繼承xxx-parent, 更不需要xxx-starter, 你只需要在pom.xml中添加一個依賴就搞定了:

<dependency>
    <groupId>io.vertx</groupId>
    <artifactId>vertx-web</artifactId>
    <version>3.5.4</version>
</dependency>

長久以來,很多人都一直在指責Java"又臭又長", 開發速度慢,運行時笨重,Tomcat更重,IDE難用(主要指Eclipse)等槽點,當然,他們說的都沒錯,錯就錯在Servlet。Servlet在當時(2001年)的編程環境下確實起到了Web開發大一統的作用,但慢慢的很多躁動的程序員們就會不滿這種呆板笨重編程方式。好了扯遠了,我們先來看看上面的幾行代碼都幹了什麼:

(1): 構造一個Vertx對象,這裏暫時沒什麼好說的

(2): 創建了一個HttpServer對象

(3): 註冊一個請求處理器,這裏我們無論什麼請求,只要來了就返回一個"it works!"字符串

(4): 讓Server在8080端口上監聽

是不是非常的直觀?

我知道,做爲一個有上進心的開發者,我們怎麼能不關心你這幾行代碼能抗多少請求呢?業務代碼寫在哪?錯誤處理?先不要着急,以後會講到的,這裏只是想告訴大家,用Java寫Http Server,其實還有更優雅的方式。

Verticle

Verticle是Vert.x裏最重要的概念。之前我們在準備篇《C10K問題與Reactor模式》中講過什麼是Reactor, 這裏的Verticle其實就是Reactor的一個實現,即事件循環。 只不過,Vert.x有一個"部署"的概念,每一個Verticle需要先進行部署,且Vert.x保證一個Verticle在整個Vertx生命週期內一定只由同一條線程來執行,這樣就避免了線程安全問題。等等,這說的好像跟Netty裏的EventLoop有點像?對,事實是,Vert.x底層就是Netty, Verticle直接就是使用Netty的EventLoop執行的。可能有人會問,爲什麼不直接用Netty? 哦,如果想用Netty的話,可以先去看看官網的Hello World需要多少行代碼吧。

下面我們來看一下一個比較"正常"的Vert.x HTTP Server應該怎麼寫:

VertxOptions options = new VertxOptions();
        options.setEventLoopPoolSize(8);
        Vertx vertx = Vertx.vertx(options); // (1)

        DeploymentOptions depOps = new DeploymentOptions();
        depOps.setInstances(8);
        vertx.deployVerticle(HttpVerticle.class, depOps, ar -> { // (2)
            if (ar.succeeded()) {
                System.out.println("done deployment");

            } else {
                System.out.println(ar.cause());
            }
        });

(1): 我們在創建Vertx對象時設置了一個參數對象,把事件循環線程數設爲8

默認值爲 CPU核心數 * 2, 我用的是物理4核的macbook pro, 這裏如果不設置會是16,因爲有超線程技術加持。

(2): 這塊可能看起來比較複雜,其實非常簡單。前面說過Verticle需要部署,那麼這就是編程式部署的代碼了。調用deployVerticle()方法,第一參數是我們想要部署的Verticle對象本身(下面會給出代碼); 第二個參數是部署參數,這裏我們設置部署8個Verticle; 第三個參數用來註冊一個回調方式,部署完成或失敗時Vert.x會使用NIO線程調用此方法。

在Vert.x裏會經常看到這種異步回調的方式,類似於Node.js, 前期需要適應一下。

接下來看看主解HttpVerticle長什麼樣:

 public static class HttpVerticle extends AbstractVerticle { // (0)
        @Override
        public void start(Future<Void> startFut) throws Exception {
            vertx.createHttpServer()
                    .requestHandler(req -> req.response().end("it works!"))
                    .listen(8080, result -> { // (1)
                        if (!result.succeeded()) { // (2)
                            System.out.println("failed to start server, msg = " + result.cause());
                            startFut.fail(result.cause()); // (3)
                        }
                    });
        }
    }

對,基本上就是之前我們的4行代碼。

(0): 我們需要繼承AbstractVerticle來編寫自己的Verticle

(1): 監聽8080端口也需要註冊一個回調方法,這樣才能知曉是否監聽成功。

(2): 判斷是否發生了錯誤

(3): 如果出錯,調用方法參數中傳過來的Future對象的fail()方法,作用是通知Vertx這個Verticle啓動失敗了,這樣上面deployVerticle()中註冊的回調裏的失敗邏輯纔會被調用。

這裏有朋友肯定會想,EventLoopPoolSize跟verticle的Instances到底應該設置多少合適呢?前面說了,一個Verticle總是由同一條NIO線程執行,如果NIO線程數與verticle數相同,那麼Vertx會保證每一個verticle都會有一條專有線程執行;如果NIO > verticle, 那麼會有NIO線程空閒; 如果 NIO < verticle, 那麼會出現一個NIO線程負責執行多個verticle的情況。這裏要注意的是,第三種情況也不會有線程安全問題,因爲一個verticle還是總是由同一條線程執行的。

着急的程序員可能已經不滿足於上面這種Hello World玩具代碼了,他們想盡快把自己的業務代碼塞進去。如果想發揮異步編程的全部實力,那麼業務代碼必須也要"異步"起來,即所有的阻塞調用,都要通過註冊事件-->回調的方式執行,萬萬不可阻塞NIO線程。說白了,就是爭取讓NIO線程一直在幹需要CPU發力的活,一刻也不能因爲等待I/O而停下來。應該怎麼辦呢,下期再說。

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