【入門指南】node.js

Eric_zhang發表於2019-04-15

node.js簡介

  • node.js不是一種獨立語言,是一個可以讓js在伺服器端執行的平臺
  • 區別與傳統語言平臺依靠多執行緒來實現高併發的設計思路,node採用單執行緒、非同步式I/O、事件驅動式的程式設計模型
  • node.js使用的JavaScript引擎為V8引擎,還使用了高效的 libev 和 libeio 庫支援事件驅動和非同步式 I/O 來代替傳統平臺的多執行緒模式,帶來效能的提升(傳統多執行緒模式對於 高併發的訪問,一方面執行緒長期阻塞等待,另一方面為了應付新請求而不斷增加執行緒,因此 會浪費大量系統資源,同時執行緒的增多也會佔用大量的 CPU 時間來處理記憶體上下文切換, 而且還容易遭受低速連線攻擊。)
  • 有別於php等語言,在使用php構建web服務時,必須外部依賴一個Nginx、Apache等http伺服器,然後通過http服務區的模組載入或CGI呼叫,才能將php的執行結果呈現給使用者;而node.js內建了http伺服器模組,無需再額外搭建
  • node.js可以呼叫c/c++程式碼,對於效能要求比較高的模組可以使用c/c++來實現
    【入門指南】node.js

【入門指南】node.js

node.js只在第一次執行時會讀取檔案內容,以後都會直接訪問記憶體,也就是當檔案實時改動時,不會觸發重新編譯檔案內容,所以需要使用supervisor、pm2等工具,對node服務進行實時管理

非同步式I/O 與事件驅動式程式設計

阻塞與執行緒

什麼是阻塞(block)呢?執行緒在執行中如果遇到磁碟讀寫或網路通訊(統稱為 I/O 操作), 通常要耗費較長的時間,這時作業系統會剝奪這個執行緒的 CPU 控制權,使其暫停執行,同 時將資源讓給其他的工作執行緒,這種執行緒排程方式稱為 阻塞。當 I/O 操作完畢時,作業系統 將這個執行緒的阻塞狀態解除,恢復其對CPU的控制權,令其繼續執行。這種 I/O 模式就是通 常的同步式 I/O(Synchronous I/O)或阻塞式 I/O (Blocking I/O)

非同步式 I/O (Asynchronous I/O)或非阻塞式 I/O (Non-blocking I/O)則針對 所有 I/O 操作不採用阻塞的策略。當執行緒遇到 I/O 操作時,不會以阻塞的方式等待 I/O 操作 的完成或資料的返回,而只是將 I/O 請求傳送給作業系統,繼續執行下一條語句。當操作 系統完成 I/O 操作時,以事件的形式通知執行 I/O 操作的執行緒,執行緒會在特定時候處理這個 事件。為了處理非同步 I/O,執行緒必須有事件迴圈,不斷地檢查有沒有未處理的事件,依次予 以處理。

多執行緒與單執行緒的比較:

對於需要長時間計算的I/O處理操作

  • 多執行緒模式:如果想實現高併發,就必須開啟多個執行緒

    【入門指南】node.js

  • 單執行緒模式:I/O非同步,通過事件方式通知執行緒,即可完成高併發

    【入門指南】node.js

單執行緒事件驅動的非同步式 I/O 比傳統的多執行緒阻塞式 I/O 究竟好在哪裡呢?

  • 簡而言之, 非同步式 I/O 就是少了多執行緒的開銷。對作業系統來說,建立一個執行緒的代價是十分昂貴的, 需要給它分配記憶體、列入排程,同時線上程切換的時候還要執行記憶體換頁,CPU 的快取被 清空,切換回來的時候還要重新從記憶體中讀取資訊,破壞了資料的區域性性。(基於多執行緒的模型也有相應的解決方案,如輕量級執行緒(lightweight thread)等。事件驅動的單執行緒非同步模型與多線 程同步模型到底誰更好是一件非常有爭議的事情,因為儘管消耗資源,後者的吞吐率並不比前者低)
  • 非同步式程式設計的缺點在於不符合人們一般的程式設計思維,存在回撥地獄的情況(使用async、promise等編寫方式)
    【入門指南】node.js

node.js的事件迴圈機制

Node.js 程式由事件迴圈開始,到事件迴圈結束,所有的邏輯都是事件的回撥函式,所以 Node.js 始終在事件迴圈中,程式入口就是 事件迴圈第一個事件的回撥函式。事件的回撥函式在執行的過程中,可能會發出 I/O 請求或 直接發射(emit)事件,執行完畢後再返回事件迴圈,事件迴圈會檢查事件佇列中有沒有未 處理的事件,直到程式結束.

與其他語言不同的是,Node.js 沒有顯式的事件迴圈,類似 Ruby 的 EventMachine::run() 的函式在 Node.js中是不存在的。Node.js的事件迴圈對開發者不可見,由libev庫實現。libev支援多種型別的事件,如 ev_io、ev_timer、ev_signal、ev_idle 等,在 Node.js 中均被 EventEmitter 封裝。libev 事件迴圈的每一次迭代,在 Node.js 中就是一次 Tick,libev 不 斷檢查是否有活動的、可供檢測的事件監聽器,直到檢測不到時才退出事件迴圈,程式結束

【入門指南】node.js

npm包管理器

建立npm包

Node.js 根 據 CommonJS 規範實現了包機制,開發了 npm來解決包的釋出和獲取需求。 使用如下命令初始化建立npm包

npm init
複製程式碼

Node.js 的包是一個目錄,其中包含一個 JSON 格式的包說明檔案 package.json。嚴格符 合 CommonJS 規範的包應該具備以下特徵:

  • package.json 必須在包的頂層目錄下;
    • 作為npm包的描述性檔案,其中常用的欄位:
    • main:作為包模組的入口(Node.js 在呼叫某個包時,會首先檢查包中 package.json 檔案的 main 欄位,將其作為包的介面模組,如果 package.json 或 main 欄位不存在,會嘗試尋找 index.js 或 index.node 作 為包的介面)
    • name:包的名稱
    • description:包的簡要說明
    • version:版本字串
    • keywords:關鍵字陣列,通常用於搜尋
    • maintainers:維護者陣列,每個元素要包含 name、email (可選)、web (可選)欄位
    • contributors:貢獻者陣列,格式與maintainers相同。包的作者應該是貢獻者陣列的第一個元素
    • bugs:提交bug的地址,可以是網址或者電子郵件地址
    • licenses:許可證陣列
    • repositories:倉庫託管地址陣列
    • dependencies:包的依賴,一個關聯陣列,由包名稱和版本號組成
  • 二進位制檔案應該在 bin 目錄下;
  • JavaScript 程式碼應該在 lib 目錄下;
  • 文件應該在 doc 目錄下;
  • 單元測試應該在 test 目錄下。 Node.js 對包的要求並沒有這麼嚴格,只要頂層目錄下有 package.json,並符合一些規範 即可。當然為了提高相容性,我們還是建議你在製作包的時候,嚴格遵守 CommonJS 規範

本地模式與全域性模式

全域性模式的安裝:

npm [install/i] -g [package_name]

為什麼要使用全域性模式呢?多數時候並不是因為許多程式都有可能用到它,為了減少多 重副本而使用全域性模式,而是因為本地模式不會註冊 PATH 環境變數。舉例說明,我們安裝 supervisor 是為了在命令列中執行它,譬如直接執行 supervisor script.js,這時就需要在 PATH 環境變數中註冊 supervisor。npm 本地模式僅僅是把包安裝到 node_modules 子目錄下,其中 的 bin 目錄沒有包含在 PATH 環境變數中,不能直接在命令列中呼叫。而當我們使用全域性模 式安裝時,npm 會將包安裝到系統目錄,譬如 /usr/local/lib/node_modules/,同時 package.json 文 件中 bin 欄位包含的檔案會被連結到 /usr/local/bin/。/usr/local/bin/ 是在PATH 環境變數中預設 定義的,因此就可以直接在命令列中執行 supervisor script.js命令了

使用全域性模式安裝的包並不能直接在 JavaScript 檔案中用 require 獲 得,因為 require 不會搜尋 /usr/local/lib/node_modules/

【入門指南】node.js
區別:

  • 當我們要把某個包作為工程執行時的一部分時,通過本地模式獲取
  • 如果要 在命令列下使用,則使用全域性模式安裝

建立全域性連結

npm 提供了一個有趣的命令 npm link,它的功能是在本地包和全域性包之間建立符號鏈 接。我們說過使用全域性模式安裝的包不能直接通過 require 使用,但通過 npm link命令 可以打破這一限制。舉個例子,我們已經通過 npm install -g express 安裝了 express, 5 這時在工程的目錄下執行命令: npm link express ./node_modules/express -> /usr/local/lib/node_modules/express 我們可以在 node_modules 子目錄中發現一個指向安裝到全域性的包的符號連結。通過這種方法,我們就可以把全域性包當本地包來使用了

除了將全域性的包連結到本地以外,使用 npm link命令還可以將本地的包連結到全域性。 使用方法是在包目錄( package.json 所在目錄)中執行 npm link 命令。如果我們要開發 一個包,利用這種方法可以非常方便地在不同的工程間進行除錯和測試

具體使用案例:

現在有兩個模組,npm-link-module為我們開發的npm包;npm-link-test為我們要使用上面的npm包來做測試

cd npm-link-module  //進入開發的npm包目錄裡
npm link //將本地的包連結到全域性

cd npm-link-test  //進入到測試專案中
npm link npm-link-module  //將全域性包npm-link-module 連結到本地node_modules路徑下,這樣本地就可以使用require()引用了
複製程式碼

包的釋出

  • npm adduser 建立賬號
  • npm whoami 命令測驗是否已經取得了賬號
  • npm publish 釋出包
  • 通過https://www.npmjs.com/ 去搜尋自己釋出的包
  • 日後有更新時,更改package.json中的version版本,重新npm publish即可
  • npm unpublish 取消已釋出的包

node命令列除錯(除錯功能由 V8 提供)

使用node debug xxx.js 即可開啟除錯模式

【入門指南】node.js