從process.versions了解Node.js的構成

看了下Node.js最新的milestone,V7.0.0在九月底十月初的樣子就要發布了,V8引擎也要升級到了最新的5.4,這就意味著在Node.js中不需要藉助Babel你也可以用到ES7的async,await特性來處理非同步了,想想還有點小激動呢?

看到node的V8升級了,我就想起我跟一些同學介紹Node.js時一般都會說"Node.js並不是平地高樓,他基本上就是一個V8引擎和libuv的一個封裝"。好了,那麼問題來了,既然說是基本,那麼完整的Node.js到底包含哪些模塊呢,這些模塊的當前版本又是什麼呢?其實我們只需要通過查看process.versions,就能知道了。

process.versions

運行node -e "process.version"或者執行node -v,就可以看到你當前使用的node版本信息,而運行node -e "process.versions"你就可以得到: 這是我在Node.js-V6.0.0下運行的結果,我們用nvm切換Node.js的版本到V4.2.2再看看: 我們看到雖然相差兩個大版本,但是兩者的很多模塊還是差別不大的。這些模塊中,像v8,uv,openssl這些,我們很熟悉,但是還有很多大部分同學估計不是很清楚,那我們就一個一個的講一下吧。

http_parser

顧名思義,這是一個用於解析http數據包的模塊。打開Node.js源碼中的deps找到http_parser,readme文檔里描述很清楚,這是一個純C的庫,無任何系統調用,同時支持解析http請求和響應,實在是居家旅行,收發http包之良庫。而且由於是C庫,所以如果你的其他語言的項目如果需要用到http包解析的功能,也可以拿這個模塊進行封裝。

曾經有個同學跟我聊起用Node.js開發web server的時候說到"node.js基於事件的非同步I/O確實很適合做server,但是js語言在運算這一環的弱勢,導致解析http都會消耗掉大量的運算資源"。這裡可以給予反駁的有兩點:一是js在V8下的運算性能並不是特別差,大概能達到純C的1/2,不主張Node.js做運算的原因主要是單線程模型,主線程如果被大量運算佔用,就不能處理新來的並發請求了;二是像http解析,crypto加解密這些計算密集型庫,都是使用C甚至一些彙編庫來做的,不存在這個問題。還是那句話,對性能有疑問就跑benchmark,大家可以在clone下Node.js的源碼,然後在根目錄下跑下make bench-http看下:可見,即使解析32個欄位的http頭,也可以達到12w+每秒的速度,還是很有說服力的。

v8

這個太重要了,V8作為Chrome和Node.js中的語言引擎,將一些虛擬機界很成熟的JIT,GC的解決方案引入了javascript引擎,並結合JS的語言特性,加入了Hidden Class功能,大幅度提高了對象的構建速度。附送源碼傳送門和文檔傳送門。

我們知道chrome有一個叫做chromium的開源項目支撐,所有新特性都會在chromium中實驗後加入chrome,所以,作為chrome瀏覽器js引擎的V8也是如此,對ES中新特性的支持也是非常及時的。早在2016年初v8-4.9發布時就宣布完成了對ES6特性91%的支持。

這次Node.js-v7將採用V8-5.4,不但性能有了較大提升,最主要的肯定上文說的async,await非同步操作的支持。可以想像,nodejs的非同步從callback,到Promise,Thunk,Generator,再到Async關鍵字一路走來,Promise和Async關鍵字將是寫起來最優雅的非同步解決方案,koa2也是將非同步庫有co替換為Async形式。

關於V8更多的信息,大家可以去自己找資料,對它的學習將是一個漫長而有趣的過程。

uv

我們知道Ryan Dahl在開發Node.js的時候就是希望解決一些快速開發非同步任務的問題,當時還比較了Lua和Javascript,最終非常非常非常明智的選擇了Javascript。當時可供選擇的非同步事件處理庫有libevent和Marc Lehmann的libev。隨著Node.js的日益流行,Node.js需要同時支持windows, 但是libev只能在Unix環境下運行。Windows 平台上與kqueue(FreeBSD)或者(e)poll(Linux)等內核事件通知相應的機制是IOCP。所以libuv就誕生了,它提供了一個跨平台的抽象,由平台決定使用libev或IOCP。在node-v0.9.0版本中,libuv移除了libev的內容。

隨著libuv的日益成熟,它成為了擁有卓越性能的系統編程庫。除了node.js以外,包括Mozilla的Rust在內的許多語言都開始使用libuv。這裡有一份libuv的中文教程,看完它你就可以利用libuv庫為node.js編寫一些原生的非同步擴展了。

在Node.js中一些語言層面的計算和優化V8都已經做的相當不錯,但是V8並不提供諸如訪問文件系統,網路等內核層面的能力,libuv就很擅長這方面的事。Node.js在V8這一層發起有關文件,網路等的調用,並在事件隊列中加入對應的事件,及其對應的回調,當libuv完成自己的工作之後,會帶著處理結果調用註冊的回調。需要注意的是libuv並不是單線程的,它的實現也是基於一個伴隨著Node.js啟動而初始化的線程池,對這一部分有興趣的可以看筆者的另一篇博客-Node.js中的多線程問題。

zlib

這個是幹啥的呢?他的Wiki上描述其實一個用於壓縮的庫。在Node中對應的主要功能就是GZip格式的壓縮和解壓,比如http響應體的壓縮和解壓,具體的我們可以看下Node.js中對ZLib的一個封裝的文檔:Zlib | Node.js v6.6.0 Documentation,可以看出Node.js藉助zlib可以創建同步的、非同步的、流式的壓縮或解壓縮任務。

ares

其實事實上Node.js依賴的模塊全稱叫做c-aresc-ares: library for asynchronous name resolves,是一個C語言的非同步DNS解析庫,可以很方便的和使用者的事件循環統一起來,實現DNS的非阻塞非同步解析,像libcurl, libevent, gevent都在使用。在Node.js中的使用方式也很簡單:

const dns = require(dns);dns.lookup(nodejs.org, (err, addresses, family) => { console.log(addresses:, addresses);});

需要注意的是這裡對DNS查詢提供了兩個API,dns.lookup和dns.resolve,兩者的使用方式分別是:

dns.lookup(hostname[, options], callback<err, address, family>)

dns.resolve(hostname[, rrtype], callback<err, addresses>)

二者都是將域名解析為IP,但是還是有一些區別的。首先,我們看參數,lookup的第二個參數是一個options, 而resolve的第二個參數是rrtype, options的可選項大家可以看下API文檔, 至於rrtype,提供了A, CNAME, MX, TXT..,這些屬於DNS相關的知識,大家可以看下這篇博客加以了解。其次,二者的實現方式也有所不同,lookup基本和系統提供的lookup命令一致,而resolve就有所不同,基本是從server端query結果,忽視本地的/etc/hosts里的內容。

icu

其實ICU確切來說應該算是V8的一個依賴,這是一個跨平台的Unicode解決方案,主要用於解決Unicode的編解碼的國際化問題,相信寫過代碼的人或多或少的都應該經歷過編碼問題吧。Node.js中使用的是ICU的一個子集small-icu,而且他是可選的。你可以在編譯Node.js的時候指定要使用的ICU庫,具體可以看下Node.js的Wiki。

modules

我們都知道Node.js在模塊載入上使用的是Commonjs規範,不了解的可以去看下阮一峰的Blog中關於這CommonJs的講解。這個規範規定我們可以使用require去載入一個js模塊或.node模塊,使用module.exports去暴露一個模塊。那麼具體的載入與暴露的實現是怎麼樣子的就在Modules中了。

有的同學,可能會問,我從很早就開始用Node.js了,沒感覺到modules有什麼變化啊,怎麼版本已經48了啊。其實模塊載入對Node.js來說是非常重要的一塊,它的緩存策略,路徑與文件索引策略都極大地影響著模塊的載入效率和Node.js的啟動速度。尤其是在node-v6版本發布時,官方團隊隆重介紹了全新的模塊載入系統,據說速度是Node.js 4.x的四倍以上。可見官方對其的重視。

關於更細一步的Modules機制介紹,大家可以看下Node.js Modules模塊系統這篇博客。

openssl

還記得14年名震一時的Heartbleed心臟滴血漏洞么?當時就是由於openssl的一個設計缺陷引起的,影響到的版本有:

  • OpenSSL 1.0.2-beta
  • OpenSSL 1.0.1 - OpenSSL 1.0.1f

可見node-v4.2.2也是存在這個漏洞的,那麼openssl到底在Node.js中扮演一個什麼角色呢?

在Node.js中openssl主要被用於tls和crypto兩個模塊,幫助Node.js完成一些加密通信和加密演算法計算相關的工作,包括https,也是依賴openssl的。由於在加密傳輸這一塊openssl基本算是業界標準的存在,被廣泛使用,所以Heartbleed漏洞影響的範圍才會如此廣。 關於openssl更多的內容,大家自行Google或移步官方文檔。

node

node就是node的版本了啊,你難道有疑問?

所以,經過對process.versions的輸出進行逐個考究,得到一個大致如下的架構圖:

你可能要問npm呢?首先,npm是一個獨立的項目,現在應該說是獨立的商業項目了。其次,npm跟Node.js的版本並不是強綁定,你可以通過npm install npm@version -g去選擇你要使用的npm版本。倒是默寫模塊,由於使用了一些npm的新特性,導致一些老版本的npm無法正確執行安裝腳本,所以說模塊反而更像是與npm關係更甚。

引用

  1. Dependencies
  2. GitHub - nodejs/node: Node.js JavaScript runtime
  3. GitHub - v8/v8: The official mirror of the V8 git repository

推薦閱讀:

nodejs 應該學習哪些框架?
typescript調用js(node)組件,必須在每一個引用的地方都寫reference嗎?
webpack這個js模塊管理器(module bundler)怎麼樣?
應該使用 const 定義 object 和 array 嗎?
前端開發中的一些框架,插件,git使用真的很重要麼?

TAG:Nodejs | 前端開發 |