公元2014年,Java 第八代國王終於登上了王位。
第一次早朝,國王坐在高高的寶座上,看著畢恭畢敬的大臣,第一次體會到了皇權的威力。
德高望重的IO大臣顫悠悠地走上前來:“啟稟陛下,昨日收到戰報,有個叫做Node.js的番邦又一次向我國進攻,我邊防將士死傷慘重。”
“Node.js? 那是什麼東西?” 國王心中一樂, 還真有人自不量力,想蚍蜉撼樹。 想我Java帝國人口之眾多,疆域之廣闊,踩死你小番邦還不像踩死一隻螞蟻似的。
“那是用JavaScript寫的一個框架。” IO大臣看到國王不知道Node.js,心裡一沉。
“JavaScript? 愛卿說笑了,一個在瀏覽器中執行的東西,怎麼可能進攻我Java後端。”
“陛下有所不知,這JavaScript發展迅猛,不僅佔領了前端,還透過Node.js向後端,尤其是我國滲透,臣還聽說他們用Electron開始蠶食桌面開發了!”
“竟有這等事!難道他們想通吃? 我們不是有Tomcat嗎? 派Tomcat去把Node.js給鎮壓了。”
國王開始怨恨自己的父親JDK 7世和祖父JDK 6世沒把這個Node.js當成一回事,沒有把Node.js給扼殺在搖籃之中,把這個禍害留給了自己,心裡開始發虛。
執行緒大臣走上前來:“陛下,Tomcat已經率軍和Node.js惡戰了幾日,敗下陣來, 這Node.js有個獨門武器,叫做‘非阻塞非同步IO’。”
“非阻塞? 我聽說我們的Tomcat也能實現非阻塞啊!” 王國有點驚訝。
“不行的,陛下,Tomcat在處理連線的時候能實現非阻塞,但是在真正處理請求的時候還是需要同步操作,一個請求對應一個執行緒來處理,不像Node.js那樣,都是非同步操作,只有一個主執行緒在忙活。” 執行緒大臣做了一個簡明扼要的彙報,不知道國王能否聽懂。
(碼農翻身老劉註: Node.js的故事請參見《Node.js: 我只需要一個店小二》)
“眾位愛卿,你們說說該怎麼辦? 總不能讓這小小番邦屢次欺負我堂堂Java帝國吧。”
“臣倒是有一計,” 集合大臣說道,“這Node.js雖然來勢洶洶,但是它也有個致命的缺點,那JavaScript是個動態語言,無法進行編譯時型別檢查,錯誤只有等到執行時才能暴露出來。用它開發個小專案還可以,一旦專案變大,程式碼變多,人員變多,那就會變成噩夢了。”
“愛卿說說具體怎麼辦?”
“我們可以派一些臥底去Node.js, 到處傳播這樣的訊息,瓦解他們的軍心和士氣,讓他們認為Node.js寫的系統,很快就會腐化,最終還是要用我堂堂正正的Java語言來重寫。”
“嗯,此乃心理戰也,至少會穩住一些牆頭草,準奏,由愛卿來安排。 ” 國王說道,“不過,此法治標不治本,還是得想辦法直接把他們打敗。”
“陛下真乃一代聖君,” 執行緒大臣馬上開始拍馬屁,與此同時,巧妙地把矛頭轉向老不死的IO大臣:“我Java帝國在第4代國王的時候就出現了非阻塞IO,這麼多年過去了,居然還沒發展出類似Node.js的系統,實在是不應該啊。”
“老不死”的IO大臣是何等精明:“陛下明鑒, 我Java帝國應用伺服器一直以來都是Tomcat獨大,他們採用了執行緒池,每個請求一個執行緒的方式,我也不好干預。”
IO大臣把責任推得一干二凈。
“沒錯,” 集合大臣為IO大臣打抱不平,兩肋插刀,“還有一點就是這非同步程式設計,聽起來很好,但是寫起來可就要命了,那麼多的回呼,簡直就是反人類,臣民們戲稱為回呼地獄,沒人願意那麼寫,發展不起來也很正常。”
執行緒大臣馬上介面:“此言差矣,陛下已經教會了臣民們如何使用Lambda運算式,並且現在也出現了RxJava,已經沒什麼回呼地獄了!”
“那是現在,以前可沒有!”
“……”
國王看到這幾位大臣要打起來,馬上施展和稀泥之術:“眾位愛卿各有道理,你們且說說,怎麼才能打敗著來勢洶洶的Node.js吧。”
沒人說話。
國王只好退朝。
京城的小酒館向來是一個多方訊息的集散地。
一個金髮碧眼的小夥子正在“危言聳聽”:“聽說了沒有,Node.js又贏了幾仗,Tomcat大軍死傷慘重,有不少臣民都投奔到那個番邦去了。”
“這非同步操作真的有這麼厲害?” 有人問道。
小夥子喝了一口酒: “其實不是非同步操作更好,而是在高併發的環境非同步操作更有效,大家都知道, 一個機器能支援的執行緒數目是有限的,不可能一直增加。Tomcat那種一個請求一個執行緒的方式很快就會遇到瓶頸。”
“你說說,到底有什麼好處?”有人刨根問底。
“現在伺服器端的操作無非就是操作檔案,讀寫資料庫,訪問遠端服務,這些都是所謂阻塞操作。” 小夥子展開了一張圖:
“橙色的都是IO操作,綠色的才是真正的執行緒執行, IO操作非常耗時,執行緒大部分時間都浪費在了等待上面! 如果能讓執行緒不要等待,去做別的事情,那用少量的執行緒,甚至一個執行緒就可以了。”
眾人紛紛點頭, 這小夥子已經看出了問題的關鍵,現在的很多系統,都是IO密集的, 高併發情況下,如果一個請求一個執行緒,浪費巨大。
“想我Java 虛擬機器如此強悍,如果能實現非同步操作,那還不把Node.js秒成渣?!”小夥子狠狠地用手錘了一下桌子。
正在此時,酒館衝進一隊士兵,趕走眾人,圍住小夥子,領頭的喝問到:“大膽刁民,竟然到處宣揚非同步思想,給我帶走!”
士兵惡狠狠地把他五花大綁,推出門去, 留下一堆人在那裡議論紛紛。
“我讓你們把他請來,怎麼綁來了?快鬆綁!” IO大臣呵斥完下屬,轉頭親切地問道:“叫什麼名字啊?”
“小人蒂姆, Tomcat府上的幕僚。 ” 蒂姆一邊說一邊揉肩膀。
“Tomcat府上的人……” IO大臣捻著鬍鬚若有所思。
“是的,大人,我還見過您呢,您上次半夜去Tomcat府上密談……”
“住口! ” IO大臣趕緊轉換話題, “我的下屬發現你到處宣揚非同步思想,究竟要乾什麼? ”
“小人發明瞭一個系統,叫做Node.x。 ”
“為什麼不獻於Tomcat 將軍?”
“唉,小人進言多次,可是將軍不聽啊!”
“你說說看,這是個什麼東西? 是要模仿Node.js嗎?” IO大臣問道。
之前蒂姆給Tomcat將軍講述過Node.js, 他理都不理,經常是一甩袖子就走, 自己是空有一身本領卻無人賞識, 難道這IO大臣能幫自己一把? 想到此處,蒂姆精神大振。
“確實受到了它的啟發, 但是我的Node.x在架構和一些關鍵的抽象上和Node.js有很大不同。” 蒂姆不好意思地笑了笑,“先說說相同的部分,既然都是非同步操作,那肯定是透過事件驅動的,所以都有一個事件迴圈。”
IO大臣之前和Swing大臣聊過, 知道事件迴圈是怎麼回事,這是一個相當古老的概念了。
無非就是有個執行緒在檢測一個佇列,如果佇列中有事件,就拿出來處理。
“只不過我這裡有所不同,可以建立多個事件迴圈出來,比如每一個CPU核心有一個,這樣可以充分利用CPU的多核效能。” 蒂姆得意地說道。
(4個CPU core, 4個事件迴圈)
IO大臣點頭表示贊許, 他聽說Node.js好像只有一個主執行緒,沒法直接利用多核的能力。想利用多核的話還得開多個行程才行。
“你圖中的那個Hanlder就是具體的業務程式碼所在地吧? 具體長什麼樣子啊,讓我看看!” IO大臣問道。
蒂姆趕緊呈上程式碼,這是簡單的Hello World。
import io.vertx.core.AbstractVerticle;
public class Server extends AbstractVerticle {
public void start() {
vertx.createHttpServer().requestHandler(req -> {
req.response()
.putHeader("content-type", "text/plain")
.end("Hello Word!");
}).listen(8080);
}
}
這段程式碼生成了一個簡單的HTTP 伺服器, 在8080埠監聽, 每當有請求來的時候,都傳回一個字串“Hello World!”。
IO大臣一看,大為吃驚:“你這程式碼不需要外部容器,自己就搞了一個HTTP伺服器啊?”
“是的,這樣我們就完全不用Tomcat了。 我把這種類起來一個名稱,叫做Verticle, 部署以後,這個Verticle就可以和一個事件迴圈關聯了。每次有HTTP請求過來,Node.x會封裝成事件,然後分派給它處理了。”
真是個二愣子, IO大臣心想, 怪不得Tomcat對你不待見,你這個東西出來,他的位置不保啊!
IO大臣問道:“那對於資料庫查詢,你這個Handler,哦不,Verticle該怎麼寫? 查詢資料庫這麼慢,豈不是把事件迴圈都阻塞了?什麼事情都做不了了?”
“大人您忘了,我們這裡操作必須都是非同步的,查詢資料庫也不例外。”
蒂姆說著展示了一段程式碼, 透過非同步的方式來查詢資料庫。
public class DatabaseVerticle extends AbstractVerticle{
......
dbClient.getConnection(ar -> {
if (ar.succeeded()) {
SQLConnection connection = ar.result();
connection.query("select .. from...", res -> {
if (res.succeeded()) {
......
} else {
......
}
});
} else {
......
}
});
}
IO大臣感慨道:“唉,老了,真是不中用了,連非同步都忘了。對了,這些個Verticle看起來都是獨立的,是被不同的執行緒呼叫的,他們之間怎麼進行互動啊?難道也透過共享記憶體的方式?”
“大人真是厲害,一下子就問到了核心問題,不能讓他們共享記憶體,那樣就需要加鎖了,我這裡引入了Event Bus的方法,讓他們之間透過訊息傳遞。”
“嗯,不錯,實現了低耦合。”
“不僅如此,這些Verticle還可以部署到不同的JVM中,透過Event Bus實現真正的分散式通訊。” 蒂姆又丟擲一個重磅炸彈。
“如此甚好!” IO大臣愛才之心驟起, “你願不願意到老夫府上做幕僚啊?”
“小人願意追隨大人!”
“好!明日早朝,你隨我入宮,面見聖上,老夫保你一世榮華富貴。 ”
第二日早朝,IO大臣迫不及待地給國王報喜:“陛下,我Java 帝國也可以採用非阻塞非同步程式設計了!擊敗Node.js之日可待。”
IO大臣講述了昨晚的情況, 細數了Node.x的種種好處。
Tomcat將軍臉上極為難看, 趕緊阻止:“陛下不可,我Java帝國採用同步處理已經很久了,臣民們已經習慣了,現在改成非同步,怕激起民變。”
“愛卿不要低估臣民採用新技術的能力嘛, 宣蒂姆進殿,呈上程式碼。”
蒂姆都不敢看Tomcat, 從懷裡掏出一張紙,雙手奉上。
vertx.createHttpServer()
.requestHandler(function (req) {
req.response()
.putHeader("content-type", "text/plain")
.end("Hello World");
}).listen(8080);
國王盯著看了半天:“嗯?不對啊,你這不是Java程式碼吧?”
Tomcat拿過國王遞過來的程式碼,掃了一眼:““大膽! 你竟然敢在朝堂之上公然宣傳JavaScript,來人,拿下!”
“陛下息怒,這是小人制定的一個策略,我的Node.x支援很多語言程式設計, 除了Java之外,還有JavaScript,Ruby, Scala, Kotlin等等。”
“哦? 是嗎? 這還能把番邦的人給吸引過來呢!你說呢,Tomcat將軍?” 國王說道。
Tomcat有些不自在,想找回場子:“嗯嗯,有一定道理,不過這個Node.x這個名字不好,拾人牙慧,讓人看低我堂堂Java帝國。”
“Node是節點的意思,朕把他改成vertex如何?也是節點的意思。”
“ 陛下聖明,可否叫做vert.x ? ” IO大臣提議。
“好,準奏,即日起,命你和蒂姆訓練臣民使用vert.x,一個月後向Node.js開戰!” 國王已經忍Node.js很久了。
不,不能讓IO大臣的Vert.x一家獨大!
國王突然想到了親爹留下來的祖訓, 帝王之術是一定要平衡朝局。
“吩咐下去,今晚朕要和Spring將軍,嗯,還有執行緒大臣,共進晚餐,朕有些事情要和他們好好談談……”