【Vert.x初體驗1】4行程式碼實現HTTP Server
4行程式碼
上古時代的Java程式設計師如果想寫一個HTTP服務,需要按下面的步驟操作:
doXXX()
一頓操作猛如虎,旁邊的小弟小妹對你佩服的五體投地。
我可能只是想說一句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而停下來。應該怎麼辦呢,下期再說。