

node.js是一個劃時代的技術(shù),它在原有的Web前端和后端技術(shù)的基礎(chǔ)上總結(jié)并提煉出了許多新的概念和方法,堪稱是十多年來Web開發(fā)經(jīng)驗(yàn)的集大成者。轉(zhuǎn)轉(zhuǎn)公司在使用node.js方面,一起走在前沿。8月16日,轉(zhuǎn)轉(zhuǎn)公司的FE王澍老師,在鏡泊湖會議室進(jìn)行了一場主題為《nodejs全棧之路》的講座。優(yōu)秀的語言、平臺、工具只有在優(yōu)秀的程序員的手中才能顯現(xiàn)出它的威力。一直聽說轉(zhuǎn)轉(zhuǎn)公司在走精英化發(fā)展戰(zhàn)略,所以學(xué)習(xí)下轉(zhuǎn)轉(zhuǎn)對node.js的使用方式,就顯得很有必要。
對于大多數(shù)人使用node.js上的直觀感受,就是模塊、工具很齊全,要什么有什么。簡單request一下模塊,就可以開始寫javasript代碼了。然而出自58同城的轉(zhuǎn)轉(zhuǎn),同樣存在大量服務(wù),使用著58自有的rpc框架scf。scf無論從設(shè)計還是實(shí)際效果,都算得上業(yè)內(nèi)領(lǐng)先。只不過在跨平臺的基礎(chǔ)建設(shè)上,略顯不足。從反編譯的源碼中,可以找到支持的平臺有.net、java、c、php。非java平臺的scf版本更新,也有些滯后。之前還聽說肖指導(dǎo)管理的應(yīng)用服務(wù)部,以“兼職”的方式開發(fā)過c++版客戶端。而且也得到umcwrite等服務(wù)的實(shí)際運(yùn)用。所以node.js解決好調(diào)用scf服務(wù),是真正廣泛應(yīng)用的前提。這也正是我最關(guān)心的問題。
王澍老自己的演講過程并沒有介紹scf調(diào)用的解決方案,但在提問環(huán)節(jié)中,進(jìn)行了解答。我能記住的內(nèi)容是,目前的采用的方案是使用node-java模塊,啟動一個jvm進(jìn)程,最終還是在node.js的項(xiàng)目中編寫的java代碼,性能尚可接受,但使用中內(nèi)存占用很大;王澍老師也在嘗試自己使用c++開發(fā)模塊來?xiàng)売胣ode-java。
這確實(shí)很讓我很失望,我所理解的node.js應(yīng)該是與性能有關(guān)的部分,幾乎全部是c++編寫的。之前肖指導(dǎo)要求發(fā)布公共服務(wù),改寫成使用scf提供的異步方式執(zhí)行,借那次機(jī)會,我也閱讀了一部分反編譯的scf源碼。感覺如果只是解決node.js調(diào)用scf的問題,不應(yīng)該是個很難的事情。像管理平臺、先知等外圍功能,可以后期一點(diǎn)點(diǎn)加入。正巧我一直在質(zhì)疑自己是不是基礎(chǔ)差的問題,干脆寫一個node.js版的scf客戶端,來試試自己的水準(zhǔn)。
結(jié)合自己之前對node.js的零散知識(其實(shí)現(xiàn)在也很零散)。對這次實(shí)踐提出如下的一些設(shè)計要點(diǎn):
1、序列化版本使用scfv3,雖然難度應(yīng)該是最大的,但應(yīng)該能在較長的時間內(nèi)避免升級序列化版本的瑣事。
2、使用管理平臺讀取配置,禁用scf.config類似的本地配置。想想之前許多部門,推進(jìn)禁用線上服務(wù)直連的過程,就覺得很有必要(管理平臺也用線下環(huán)境,線下調(diào)試根本不是阻礙)。
3、客戶端支持全類型,之前偶爾聽說了c++版客戶端不支持枚舉類型,使得有些服務(wù)只能調(diào)整接口。
4、c++使用libuv庫,具備跨平臺開發(fā)、調(diào)試能力。c++版客戶端聽說只支持linux平臺。
5、只提供異步接口,這是當(dāng)然的,不然node.js就別想用了。
現(xiàn)有的c++客戶端,在3、4、5上與我的設(shè)想不符合,所以我決定親自編寫。
先是搜了本介紹libuv的pdf——《An Introduction to libuv》,看了幾天,對libuv的使用方式有所了解,用上的只有tcp相關(guān)接口。(比起java,node.js的資料還是少,介紹的也少有深入的,像這樣的底層類庫,資料就更少了)
在58作為rd,如果不是做ios,是少有配macbook的員工的。所以我本次是在windows上編寫的。不得不說node.js就是霸道,自動安裝時,默認(rèn)全部安裝最新的版本。這樣在windows平臺上編譯c++時,就要求visual studio不能低于2015。
網(wǎng)上搜索c++開發(fā)node.js模塊,基本總是能找到那個addon的示例??赡苁怯捎趘8引擎的接口也有過變化,addon的示例使用的類型、接口也存在幾種,終于還是試出了自己可以編譯過的了。
首先在addon的基礎(chǔ)上,寫個運(yùn)用libuv連接tcp的邏輯,一旦試通了,就可以一點(diǎn)點(diǎn)抄寫反編譯的scf客戶端源碼了。
在開發(fā)過程中,我的設(shè)計也進(jìn)行了一些修改:
1、反序列化邏輯,通過tcp連接,交由一個java程序來執(zhí)行(基于netty開發(fā))。由于反序列化時,scf的二進(jìn)制數(shù)據(jù)是沒有足夠的類型信息的。大體上,當(dāng)讀取到一個typeid時,如果本地沒有對應(yīng)的類型信息,完全不知道下一個字節(jié)是做什么用的。(我其實(shí)只希望得到一個類似多叉樹的嵌套格式,也做不到。)如果非要使用c++來執(zhí)行反序列化,也并非不可能。需要將scf反序列化用到的類型信息,整理成一種新的數(shù)據(jù)格式,存放于c++程序的內(nèi)存中。為此需要開發(fā)一個輸出類型配置數(shù)據(jù)的java離線工具,node.js模塊需要開發(fā):讀取這個類型配置文件到內(nèi)存,再將scf反序列化的邏輯使用c++抄一遍。綜上來看,使用一個java的反序列化輔助進(jìn)程,可以在性能幾乎無損的情況下,極大的減少了開發(fā)量,同時避免了許多反序列化過程中的bug。這不正是一個極簡的微服務(wù)嘛。
2、javascript入?yún)ο笾?,需要自帶scf序列化相關(guān)的類型信息,這樣就能在全類型的支持scf對象了。當(dāng)然我也設(shè)想過,有沒有機(jī)會將序列化,也交由java輔助進(jìn)程。那樣就需要設(shè)計一個java對象在javascript中的表示形式,由java輔助進(jìn)程,先轉(zhuǎn)換為java對象,再序列化。再加上兩次額外tcp傳輸。在沒有減少工作量的情況下,浪費(fèi)了不少性能。當(dāng)然如果十分拒絕c++開發(fā)的話,倒是能因此少寫些c++代碼。
后續(xù)可以做的一些事情:
1、完善的重連、超時處理;
2、管理平臺配置熱更新;
3、管理平臺數(shù)據(jù)上報;
4、先知;
5、加密、壓縮(似乎和node.js的非計算密集場景有些沖突,而且公司的scf配置默認(rèn)都是關(guān)閉這兩個的。scf良好的用了這么些年,不開啟這兩個功能的功勞應(yīng)該也不?。?/p>
當(dāng)然已開發(fā)的內(nèi)容中,也一定滿是bug。等有人用了,我再考慮改bug的事。生產(chǎn)環(huán)境下的試錯機(jī)會,才能讓程序真正成長。
(待續(xù))