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而停下來。應該怎麼辦呢,下期再說。