node.js實例教程(Nodejs開發實戰)

本文目錄一覽:

node.js 基礎操作

require 函數用來在一個模塊中引入另外一個模塊。傳入一個模塊名,返回一個模塊導出對象。用法: let cc = require(“模塊名”) ,其中模塊名可以用絕對路徑也可以用相對路徑,模塊的後綴名.js可以省略。例如:

require()函數用兩個作用:

exports 對象用來導出當前模塊的公共方法或屬性,別的模塊通過 require 函數使用當前模塊時得到的就是當前模塊的 exports 對象。用法: exports.name ,name為導出的對象名。例子:

module.exports 用來導出一個默認對象,沒有指定對象名,常見於修改模塊的原始導出對象。比如原本模塊導出的是一個對象,我們可以通過module.exports修改為導出一個函數。如下:

3.加載第三方包

Node.js中使用 CommonJs 模塊化機制,通過 npm 下載的第三方包,我們在項目中引入第三方包都是: let xx = require(‘第三方包名’) ,究竟 require 方法加載第三方包的原理機制是什麼,今天我們來探討下。

require(‘第三方包名’) 優先在加載該包的模塊的同級目錄 node_modules 中查找第三方包。

找到該第三方包中的 package.json 文件,並且找到裏面的 main 屬性對應的入口模塊,該入口模塊即為加載的第三方模塊。

如果在要加載的第三方包中沒有找到 package.json 文件或者是 package.json 文件中沒有 main 屬性,則默認加載第三方包中的 index.js 文件。

如果在加載第三方模塊的文件的同級目錄沒有找到 node_modules 文件夾,或者以上所有情況都沒有找到,則會向上一級父級目錄下查找 node_modules 文件夾,查找規則如上一致。

如果一直找到該模塊的磁盤根路徑都沒有找到,則會報錯: can not find module xxx 。

4.npm命令

npm 英文全稱: node package manager ,npm 為你和你的團隊打開了連接整個 JavaScript 天才世界的一扇大門。它是世界上最大的軟件註冊表,每星期大約有 30 億次的下載量,包含超過 600000 個 包(package) (即,代碼模塊)。來自各大洲的開源軟件開發者使用 npm 互相分享和借鑒。包的結構使您能夠輕鬆跟蹤依賴項和版本。我們平時開發項目都是需要使用npm下載依賴,常見的npm命令總結如下:

5.文件讀取

var fs = require(‘fs’)

同步:

var content = fs.readFileSync(‘hello.txt’,{flag:’r’,encoding:”utf-8″})

異步(默認):

flag:讀取模式

encoding:編碼格式

7.文件寫入

var fs = require(‘fs’)

格式:write=w read=r append =a

異步:

8.文件刪除

fs . unlink ( ‘lc.txt’ , function (){

9.buffer緩衝區

1、數組不能進行二進制數據的操作2、js數組不像java、python等語言效率高3、buffer內存空間開闢出固定大小的內存

let buf1 = Buffer.alloc(10)

console.log(buf1)

allocUnsafe(之前的一些內容)(效率高)

10.文件目錄

var fs = require(‘fs’)

fs.readdir(path,callback)

導入 readline 包

let readline = require(‘readline’);

實例化接口對象(process對象,stdout/in輸入輸出)

question方法 提問

close 事件監聽

11.文件流

var fs = require(‘fs’)

語法: fs.createWriteStream(文件路徑,【可選的配置操作】)

let ws = fs.createWriteStream(“hello.txt”,{flags:”w”,encoding:”utf-8″});

let ws = fs.createWriteStream(“hello.txt”,{flags:”w”,encoding:”utf-8″});

實踐

fs.createReadStream(路徑,【可選的配置項】)

文檔

let rs = fs.createReadStream(‘hello.txt’,{flags:’r’,encoding:”utf-8″})

音樂

let rs = fs.createReadStream(‘snake.mp4′,{flags:’r’})

讀取時寫入

let ws = fs.createWriteStream(‘a.txt’,{flags:”w”,encoding:”utf-8″})

createReadStream.pipe(createWriteStream)

鏈式是通過連接輸出流到另外一個流並創建多個流操作鏈的機制。鏈式流一般用於管道操作。

接下來我們就是用管道和鏈式來壓縮和解壓文件。

創建 compress.js 文件, 代碼如下:

代碼執行結果如下:

執行完以上操作後,我們可以看到當前目錄下生成了 input.txt 的壓縮文件 input.txt.gz。

接下來,讓我們來解壓該文件,創建 decompress.js 文件,代碼如下:

12.node事件

Node.js 是單進程單線程應用程序,但是因為 V8 引擎提供的異步執行回調接口,通過這些接口可以處理大量的並發,所以性能非常高。

Node.js 幾乎每一個 API 都是支持回調函數的。

Node.js 基本上所有的事件機制都是用設計模式中觀察者模式實現。

Node.js 單線程類似進入一個while(true)的事件循環,直到沒有事件觀察者退出,每個異步事件都生成一個事件觀察者,如果有事件發生就調用該回調函數.

沒有使用 events 包 僅使用JavaScript事件監聽進行事件驅動

Node.js 使用事件驅動模型,當web server接收到請求,就把它關閉然後進行處理,然後去服務下一個web請求。

當這個請求完成,它被放回處理隊列,當到達隊列開頭,這個結果被返回給用戶。

這個模型非常高效可擴展性非常強,因為 webserver 一直接受請求而不等待任何讀寫操作。(這也稱之為非阻塞式IO或者事件驅動IO)

在事件驅動模型中,會生成一個主循環來監聽事件,當檢測到事件時觸發回調函數。

Node.js 有多個內置的事件,我們可以通過引入 events 模塊,並通過實例化 EventEmitter 類來綁定和監聽事件,如下實例:

以下程序綁定事件處理程序:

我們可以通過程序觸發事件:

接下來讓我們執行以上代碼:

在 Node 應用程序中,執行異步操作的函數將回調函數作為最後一個參數, 回調函數接收錯誤對象作為第一個參數。

接下來讓我們來重新看下前面的實例,創建一個 input.txt ,文件內容如下:

創建 main.js 文件,代碼如下:

以上程序中 fs.readFile() 是異步函數用於讀取文件。如果在讀取文件過程中發生錯誤,錯誤 err 對象就會輸出錯誤信息。

如果沒發生錯誤,readFile 跳過 err 對象的輸出,文件內容就通過回調函數輸出。

執行以上代碼,執行結果如下:

接下來我們刪除 input.txt 文件,執行結果如下所示:

因為文件 input.txt 不存在,所以輸出了錯誤信息。

Node.js 所有的異步 I/O 操作在完成時都會發送一個事件到事件隊列。

Node.js 裏面的許多對象都會分發事件:一個 net.Server 對象會在每次有新連接時觸發一個事件, 一個 fs.readStream 對象會在文件被打開的時候觸發一個事件。所有這些產生事件的對象都是 events.EventEmitter 的實例。

events 模塊只提供了一個對象:events.EventEmitter。EventEmitter 的核心就是事件觸發與事件監聽器功能的封裝。

你可以通過require(“events”);來訪問該模塊。

EventEmitter 對象如果在實例化時發生錯誤,會觸發 error 事件。當添加新的監聽器時,newListener 事件會觸發,當監聽器被移除時,removeListener 事件被觸發。

下面我們用一個簡單的例子說明 EventEmitter 的用法:

執行結果如下:

運行這段代碼,1 秒後控制台輸出了 ‘some_event 事件觸發’ 。其原理是 event 對象註冊了事件 some_event 的一個監聽器,然後我們通過 setTimeout 在 1000 毫秒以後向 event 對象發送事件 some_event,此時會調用some_event 的監聽器。

EventEmitter 的每個事件由一個事件名和若干個參數組成,事件名是一個字符串,通常表達一定的語義。對於每個事件,EventEmitter 支持 若干個事件監聽器。

當事件觸發時,註冊到這個事件的事件監聽器被依次調用,事件參數作為回調函數參數傳遞。

讓我們以下面的例子解釋這個過程:

執行以上代碼,運行的結果如下:

以上例子中,emitter 為事件 someEvent 註冊了兩個事件監聽器,然後觸發了 someEvent 事件。

運行結果中可以看到兩個事件監聽器回調函數被先後調用。這就是EventEmitter最簡單的用法。

EventEmitter 提供了多個屬性,如 on 和 emit 。 on 函數用於綁定事件函數, emit 屬性用於觸發一個事件。接下來我們來具體看下 EventEmitter 的屬性介紹。

如何編寫 Node.js 擴展

一、編寫Node.js原生擴展

Node.js是一個強大的平台,理想狀態下一切都都可以用javascript寫成。然而,你可能還會用到許多遺留的庫和系統,這樣的話使用c++編寫Node.JS擴展會是一個不錯的注意。

以下所有例子的源代碼可在node擴展示例中找到 。

編寫Node.js C + +擴展很大程度上就像是寫V8的擴展; Node.js增加了一些接口,但大部分時間你都是在使原始的V8數據類型和方法,為了理解以下的代碼,你必須首先閱讀V8引擎嵌入指南。

Javascript版本的Hello World

在講解C++版本的例子之前,先讓我們來看看在Node.js中用Javascript編寫的等價模塊是什麼樣子。這是一個最簡單的Hello World,也不是通過HTTP,但它展示了node模塊的結構,而其接口也和大多數C++擴展要提供的接口差不多:

HelloWorldJs = function() {

this.m_count = 0;

};

HelloWorldJs.prototype.hello = function()

{

this.m_count++;

return 「Hello World」;

};

exports.HelloWorldJs = HelloWorldJs;

正如你所看到的,它使用prototype為HelloWorldJs類創建了一個新的方法。請注意,上述代碼通過將HelloWorldJS添加到exports變量來暴露構造函數。

要在其他地方使用該模塊,請使用如下代碼:

var helloworld = require(『helloworld_js』);

var hi = new helloworld.HelloWorldJs();

console.log(hi.hello()); // prints 「Hello World」 to stdout

C++版本的Hello World

要開始編寫C++擴展,首先要能夠編譯Node.js(請注意,我們使用的是Node.js 2.0版本)。本文所講內容應該兼容所有未來的0.2.x版本。一旦編譯安裝完node,編譯模塊就不在需要額外的東西了。

完整的源代碼可以在這裡找到 。在使用Node.js或V8之前,我們需要包括相關的頭文件:

#include v8.h

#include node.h

using namespace node;

using namespace v8;

在本例子中我直接使用了V8和node的命名空間,使代碼更易於閱讀。雖然這種用法和谷歌的自己的C++編程風格指南相悖,但由於你需要不停的使用V8定義的類型,所以目前為止的大多數node的擴展仍然使用了V8的命名空間。

接下來,聲明HelloWorld類。它繼承自node::ObjectWrap類 ,這個類提供了幾個如引用計數、在V8內部傳遞contex等的實用功能。一般來說,所有對象應該繼承ObjectWrap:

class HelloWorld: ObjectWrap

{

private:

int m_count;

public:

聲明類之後,我們定義了一個靜態成員函數,用來初始化對象並將其導入Node.js提供的target對象中。設個函數基本上是告訴Node.js和V8你的類是如何創建的,和它將包含什麼方法:

static PersistentFunctionTemplate s_ct;

static void Init(HandleObject target)

{

HandleScope scope;

LocalFunctionTemplate t = FunctionTemplate::New(New);

s_ct = PersistentFunctionTemplate::New(t);

s_ct-InstanceTemplate()-SetInternalFieldCount(1);

s_ct-SetClassName(String::NewSymbol(「HelloWorld」));

NODE_SET_PROTOTYPE_METHOD(s_ct, 「hello」, Hello);

target-Set(String::NewSymbol(「HelloWorld」),

s_ct-GetFunction());

}

在上面這個函數中target參數將是模塊對象,即你的擴展將要載入的地方。(譯著:這個函數將你的對象及其方法連接到

這個模塊對象,以便外界可以訪問)首先我們為New方法創建一個FunctionTemplate,將於稍後解釋。我們還為該對象添加一個內部字段,並命

名為HelloWorld。然後使用NODE_SET_PROTOTYPE_METHOD宏將hello方法綁定到該對象。最後,一旦我們建立好這個函數模板後,將他分配給target對象的HelloWorld屬性,將類暴露給用戶。

接下來的部分是一個標準的C++構造函數:

HelloWorld() :

m_count(0)

{

}

~HelloWorld()

{

}

接下來,在::New 方法中V8引擎將調用這個簡單的C++構造函數:

static HandleValue New(const Arguments args)

{

HandleScope scope;

HelloWorld* hw = new HelloWorld();

hw-Wrap(args.This());

return args.This();

}

此段代碼相當於上面Javascript代碼中使用的構造函數。它調用new HelloWorld

創造了一個普通的C++對象,然後調用從ObjectWrap繼承的Wrap方法,

它將一個C++HelloWorld類的引用保存到args.This()的值中。在包裝完成後返回args.This(),整個函數的行為和

javascript中的new運算符類似,返回this指向的對象。

現在我們已經建立了對象,下面介紹在Init函數中被綁定到hello的函數:

static HandleValue Hello(const Arguments args)

{

HandleScope scope;

HelloWorld* hw = ObjectWrap::UnwrapHelloWorld(args.This());

hw-m_count++;

LocalString result = String::New(「Hello World」);

return scope.Close(result);

}

函數中首先使用ObjectWrap模板的方法提取出指向HelloWorld類的指針,然後和javascript版本的HelloWorld一樣遞增計數器。我們新建一個內容為「HelloWorld」的v8字符串對象,然後在關閉本地作用域的時候返回這個字符串。

上面的代碼實際上只是針對v8的接口,最終我們還需要讓Node.js知道如何動態加載我們的代碼。為了使Node.js的擴展可以在執行時從動態鏈接庫加載,需要有一個dlsym函數可以識別的符號,所以執行編寫如下代碼:

extern 「C」 {

static void init (HandleObject target)

{

HelloWorld::Init(target);

}

NODE_MODULE(helloworld, init);

}

由於c++的符號命名規則,我們使用extern

C,以便該符號可以被dysym識別。init方法是Node.js加載模塊後第一個調用的函數,如果你有多個類型,請全部在這裡初始化。

NODE_MODULE宏用來填充一個用於存儲模塊信息的結構體,存儲的信息如模塊使用的API版本。這些信息可以用來防止未來因API不兼容導致的崩

潰。

到此,我們已經完成了一個可用的C++ NodeJS擴展。

Node.js也提供了一個用於構建模塊的簡單工具:

node-waf首先編寫一個包含擴展編譯方法的wscript文件,然後執行node-waf configure

node-waf build完成模塊的編譯和鏈接工作。對於這個helloworld的例子來說,wscript內容如下:

def set_options(opt):

opt.tool_options(「compiler_cxx」)

def configure(conf):

conf.check_tool(「compiler_cxx」)

conf.check_tool(「node_addon」)

def build(bld):

obj = bld.new_task_gen(「cxx」, 「shlib」, 「node_addon」)

obj.cxxflags = [「-g」, 「-D_FILE_OFFSET_BITS=64」, 「-D_LARGEFILE_SOURCE」, 「-Wall」]

obj.target = 「helloworld」

obj.source = 「helloworld.cc」

異步IO的HelloWorld

對於實際的應用來說,HelloWorld的示例太過簡單了一些,Node.js主要的優勢是提供異步IO。

Node.js內部通過libeio將會產生阻塞的操作全都放入線程池中執行。如果需要和遺留的c庫交互,通常需要使用異步IO來為javascript

代碼提供回調接口。

通常的模式是提供一個回調,在異步操作完成時被調用——你可以在整個Node.js的API中看到這種模式。

Node.js的filesystem模塊提供了一個很好的例子,其中大多數的函數都在操作完成後通過調用回調函數來傳遞數據。和許多傳統的GUI框架一

樣,Node.js只在主線程中執行JavaScript,因此主線程以外的任何操作都不應該直接和V8或Javascript交互。

同樣helloworld_eio.cc源代碼在GitHub上。我只強調和原來HelloWorld之間的差異,其中大部分代碼保持不變,變化集中在Hello方法中:

static HandleValue Hello(const Arguments args)

{

HandleScope scope;

REQ_FUN_ARG(0, cb);

HelloWorldEio* hw = ObjectWrap::UnwrapHelloWorldEio(args.This());

在Hello函數的入口處 ,我們使用宏從參數列表的第一個位置獲取回調函數,在下一節中將詳細介紹。然後,我們使用相同的Unwarp方法提取指向類對象的指針。

hello_baton_t *baton = new hello_baton_t();

baton-hw = hw;

baton-increment_by = 2;

baton-sleep_for = 1;

baton-cb = PersistentFunction::New(cb);

這裡我們創建一個baton結構,並將各種參數保存在裏面。請注意,我們為回調函數創建了一個永久引用,因為我們想要在超出當前函數作用域的地方使用它。如果不這麼做,在本函數結束後將無法再調用回調函數。

hw-Ref();

eio_custom(EIO_Hello, EIO_PRI_DEFAULT, EIO_AfterHello, baton);

ev_ref(EV_DEFAULT_UC);

return Undefined();

}

如下代碼是真正的重點。首先,我們增加HelloWorld對象的引用計數,這樣在其他線程執行的時候他就不會被回收。

函數eio_custom接受兩個函數指針作為參數。EIO_Hello函數將在線程池中執行,然後EIO_AfterHello函數將回到在「主線程」

中執行。我們的baton結構也被傳遞進各函數,這些函數可以使用baton結構中的數據完成相關的操作。同時,我們也增加event

loop的引用。這很重要,因為如果event

loop無事可做,Node.js就會退出。最終,函數返回Undefined,因為真正的工作將在其他線程中完成。

static int EIO_Hello(eio_req *req)

{

hello_baton_t *baton = static_casthello_baton_t *(req-data);

sleep(baton-sleep_for);

baton-hw-m_count += baton-increment_by;

return 0;

}

這個回調函數將在libeio管理的線程中執行。首先,解析出baton結構,這樣可以訪問之前設置的各種參數。然後

sheep

baton-sleep_for秒,這麼做是安全的,因為這個函數運行在獨立的線程中並不會阻塞主線程中javascript的執行。然後我們的

增計數器,在實際的系統中,這些操作通常需要使用Lock/Mutex進行同步。

當上述方法返回後,libeio將會通知主線程它需要在主線成上執行代碼,此時EIO_AfterHello將會被調用。

static int EIO_AfterHello(eio_req *req)

{

HandleScope scope;

hello_baton_t *baton = static_casthello_baton_t *(req-data);

ev_unref(EV_DEFAULT_UC);

baton-hw-Unref();

進度此函數時,我們提取出baton結構,刪除事件循環的引用,並減少HelloWorld對象的引用。

LocalValue argv[1];

argv[0] = String::New(「Hello World」);

TryCatch try_catch;

baton-cb-Call(Context::GetCurrent()-Global(), 1, argv);

if (try_catch.HasCaught()) {

FatalException(try_catch);

}

新建要傳遞給回調函數的字符串參數,並放入字符串數組中。然後我們調用回調傳遞一個參數,並檢測可能拋出的異常。

baton-cb.Dispose();

delete baton;

return 0;

}

在執行過回調之後,應該銷毀持久引用,然後刪除之前創建的baton結構。

最後,你可以使用如下形式在Javascript中使用該模塊:

var helloeio = require(『./helloworld_eio』);

hi = new helloeio.HelloWorldEio();

hi.hello(function(data){

console.log(data);

});

參數傳遞與解析

除了HelloWorld之外,你還需要理解最後一個問題:參數的處理。在helloWorld EIO例子中,我們使用一個REQ_FUN_ARG宏,然我們看看這個宏到底都做些什麼。

#define REQ_FUN_ARG(I, VAR) \

if (args.Length() = (I) || !args[I]-IsFunction()) \

return ThrowException(Exception::TypeError( \

String::New(「Argument 」 #I 」 must be a function」))); \

LocalFunction VAR = LocalFunction::Cast(args[I]);

就像Javascript中的argument變量,v8使用數組傳遞所有的參數。由於沒有嚴格的類型限制,所以傳遞給函數的參數數目可能和期待的不同。為了對用戶友好,使用如下的宏檢測一下參數數組的長度並判斷參數是否是正確的類型。如果傳遞了錯誤的參數類型,該宏將會拋出TypeError異常。為簡化參數的解析,目前為止大多數的Node.js擴展都有一些本地作用域內的宏,用於特定類型參數的檢測。

二、揭秘node.js事件

要使用NodeJS,你需要知道一個重要的東西:事件(events)。Node中有很多對象都可以觸發事件,Node

的文檔中有很多示例。但文檔也許並不能清晰的講解如何編寫自定義事件以及監聽函數。對於一些簡單的程序你可以不使用自定義事件,但這樣很難應對複雜的應

用。那麼如何編寫自定義事件?首先需要了解的是在node.js中的』events』模塊。

快速概覽

要訪問此模塊,只需使用如下語句:

require(『events』)

requires(『events』).EventEmitter

特別說明,node中所有能觸發事件的對象基本上都是後者的實例。讓我們創建一個簡單的演示程序Dummy:

dummy.js

view plaincopy to clipboardprint?

// basic imports

var events = require(『events』);

// for us to do a require later

module.exports = Dummy;

function Dummy() {

events.EventEmitter.call(this);

}

10.

11. // inherit events.EventEmitter

12. Dummy.super_ = events.EventEmitter;

13. Dummy.prototype = Object.create(events.EventEmitter.prototype, {

14. constructor: {

15. value: Dummy,

16. enumerable: false

17. }

18. });

// basic imports

var events = require(『events』);

// for us to do a require later

module.exports = Dummy;

function Dummy() {

events.EventEmitter.call(this);

}

// inherit events.EventEmitter

Dummy.super_ = events.EventEmitter;

Dummy.prototype = Object.create(events.EventEmitter.prototype, {

constructor: {

value: Dummy,

enumerable: false

}

});

上述代碼中重點展示如何使用EventEmitter擴充對象,並從中繼承所有的原型對象,方法…等等。

現在,我們假設Dummy有一個cooking()的方法,一旦把食物做熟之後它會觸發』cooked』事件,並調用一個名為』eat』的回調函數。

dummy-cooking.js

view plaincopy to clipboardprint?

Dummy.prototype.cooking = function(chicken) {

var self = this;

self.chicken = chicken;

self.cook = cook(); // assume dummy function that』ll do the cooking

self.cook(chicken, function(cooked_chicken) {

self.chicken = cooked_chicken;

self.emit(『cooked』, self.chicken);

});

10. return self;

11. }

Dummy.prototype.cooking = function(chicken) {

var self = this;

self.chicken = chicken;

self.cook = cook(); // assume dummy function that』ll do the cooking

self.cook(chicken, function(cooked_chicken) {

self.chicken = cooked_chicken;

self.emit(『cooked』, self.chicken);

});

return self;

}

如何在 Windows 10 中搭建 Node.js 環境

準備工作

在 Windows 中用 Node.js 進行開發一度是非常麻煩的事,但是現在這一狀況相較於一兩年前有了較大改善。這也是為什麼,在選擇 Windows 7 還是 Windows 10 作為本文主題之時,我們猶豫不決的原因。

儘管 Windows 7 仍舊非常流行,而且 Windows 10 有一些不好的風聞(由於評價標準及數據收集範圍的不同),我們還是決定選擇 Windows 10 為試驗對象,因為確保最新的操作系統對保證應用安全至關重要。

在本文中,我們將儘可能使用最新的工具與應用(並使用其64位版本)。筆者知道在公司環境中這可能無法保證,但保持工具的前衛是很重要的。

本文所有的安裝都會在本機中進行。我不建議在 Cygwin 中搭建 Node 環境。此外,儘管 VirtualBox 是免費的,當我在 Windows 機器上運行 Linux 虛擬機時,卻總是問題不斷。

步驟1:安裝 Git

首先,安裝 Git。使用默認設置,這些設置是相當合理的。

筆者通常會在主目錄下創建一個項目文件夾。設置時,右鍵單擊該文件夾,選擇 「Git bash here」,再通過 git –version 指令檢查 git 版本。

這是很好的 bash 環境,你可以創建一個 .bash_profile ,在你打開 bash 窗口時執行。此外,這不是 cmd.exe 窗口,你可以查看一些選項(單擊左上角的圖標)。你可以通過鼠標中鍵將文本拷貝至窗口(就像在創建的 Linux 終端一樣)。

步驟2:在 Windows 10 上安裝 Node.js

下載並 安裝 Node.js 。使用其 LTS(長期支持)版本。

筆者不建議並排安裝多個版本,因為 Node 版本管理器並未正式支持 Windows ——不過,你仍有一些備選方案,比如 nvm-windows 或 nodist 。其實,即便是在其他系統中,全局安裝不同版本的 node 工具仍然像是在自找麻煩。

步驟3:更新 npm

npm 伴隨着 Node 而來。成功安裝 Node.js 之後,包管理器 npm 也應當可用了。

打開一個 bash shell,通過 npm –version 檢查版本號。如果 npm 是 2.x 版本,則應該升級到版本3,這能解決許多問題(對我們而言,最重要的是其處理對等依賴的方式)。在開始菜單中搜索 Power Shell,以管理員身份運行,並遵循 以下步驟 。

步驟4:安裝Visual Studio 與Python

Node 包通常會依賴帶有本地代碼的包,因此你必須安裝 Visual Studio。

Node-gpy 是圍繞 Python GYP (Generate Your Projects)的一款包裝程序,該工具能為 Gcc, XCode 以及 Visual Studio 生成項目文件。由於 Windows 開發實際上是通過 Visual Studio 進行的,我們會用其支持 Visual Studio。

安裝 Python(2.x 版本)

如你所見,你會用到 Python,因此 下載其64位的 2.x 版本 並安裝之。你可以遵循默認設置,並選擇 「Add to path (添加至路徑)」選項。這會將 Python 二進制添加到全局路徑,意味着最終你要先登出再登陸。

下一步,進入環境變量設置(在系統,高級設置中),並將GYP_MSVS_VERSION=2015 添加到全局變量中,因為下一步是 Visual Studio 2015 的安裝。

安裝 Visual Studio (VS2015)

不同於2012之前的版本,VS2015 能與64位的 Node.js 和諧工作。很快,我們將學習 Node-gyp 針對 Windows 10 的教程 。

除非你的機器上已經安裝了完整的 VS,請下載 Visual Studio 2015 社區版 ,選擇自定義安裝並選定完全的 Visual C++ 分支(不帶 XP 支持),此外,在工具中選擇Windows SDKs。如果在安裝過程中出現任何差錯,你可以點擊程序與特性(Programs and Features),選擇 VS2015,進行更改與修正。

在 gyp 的安裝手冊中還提到了 Windows 7 SDKs,但是我們在前面已經安裝了 Win 8 SDKs,所以希望不會用到 Win 7 SDKs。

步驟5:安裝包依賴

目前,筆者正在開發 Trace 中的告警微服務,所以我會通過 npm -i 指令安裝所需的包依賴。得到的結果如下圖所示:

Fsevents 是可選依賴,且只能用於 OSX 系統;這只是一個警告——其餘模塊並無問題。

該微服務用到了 Postgres 與RabbitMQ,因此筆者也安裝了二者(連同 Erlang)。此處,與 OSX brew(與 apt、Chocolatey 相似的一款包管理器)以及 rocket(一款服務管理器)配置相比,唯一的不同是我必須 手動在 15672 端口啟用 web 管理員 。

在數據庫端,筆者添加了默認用戶,並創建了一個數據庫。不過,這些都可以在 PgAdmin 客戶端輕鬆完成。

步驟6:處理環境變量

通常,Node.js 項目都高度依賴環境變量。

從上面的項目截圖中可以看到,IS_INTERACTIVE 是一個環境變量(env var),這在 Linux 與 OSX 系統中很容易定義,但是在 Windows 中則有一點不同。

在 package.json 的腳本部分,你可以使用安裝在本地的 node 模塊。筆者建議你盡量避免通過 npm -g 指令全局地安裝包。

此外,筆者也不建議在 Windows (更精確地說,在跨平台項目中)的腳本部分直接添加環境變量,其實,我們有別的選擇。

Npm 會直接將這些指令 傳遞至 OS ,在本例中,傳遞到 NT 命令解釋器(cmd.exe)。此處,最快捷的解決辦法是將腳本行拷貝到我們的 bash 窗口,並運行之。但是,理所當然,這不是長遠的解決辦法。最新發佈的 Windows bash shell 支持 (目前仍處於測試階段)很可能會解決此問題。

最清楚的解決方法是對每一腳本行使用一條指令(如你所見,我們的 npm run lint 指令運行良好)。

任何依賴於 flashvars (臨時環境變量)或試圖同時完成許多操作的指令,都應該寫在某個 /scripts 文件夾下,作為 Node 可執行的JavaScript 文件。

不要使用 bash 腳本,cmd 無法處理這些腳本。Cmd.ex 支持 ,因此兩三條指令還行,將一整個 shell 腳本寫做一行就不行了(尤其不應帶有 bash 語言特性)。

為了支持腳本,這是可行的。但是為了運行我們的應用,就需要許多環境變量。

在 RisingStack,我們在開發階段會使用 nodemon (不過,有些人或許會用 pm2)。Nodemon 是一款文件監視器,會在開始時根據你定義的環境變量,解析 nodemon.json 文件。

筆者通常會在 .gitignore_global 文件(在主目錄下,記得用 git config –global core.excludesfile ~/.gitignore_global 進行初始化)中加入nodemon.* ,這樣一來,我的項目中便可以有多個 nodemon json 模板。

儘管不是非常優雅的解決方案,筆者通常會全局地安裝 nodemon。有時,在開發中直接手動啟動 nodemon,而不是通過適當的運行腳本,更為簡單。

有了上面的 json,現在可以啟動我的微服務了,如下所示:

當然,由於筆者不願監視文件變化,nodemon 可能不是最佳的僅用於運行腳本的解決方案。對於那些情況,筆者通常會將 nodemon.json 文件轉化為 nodemon.sh,將每一個環境變量導出至後者。請注意:你可以根據自己的喜好隨意命名該文件,但是不要忘記將其添加至忽略文件 ——不慎將該文件推入資源庫會造成很大的麻煩:

export NODE_ENV=”development”

export PORT=3060

export AMQP_URI=”amqp://localhost:5672/”

export EMAIL_SENDER_NAME=”Developer” #etc.

之後,筆者可以在命令行中以其為源文件(源引nodemon.dev.sh)——這樣做是為了我們當前使用的 MinGW bash,但是,如果將其轉化為傳統的 bat 文件,會更為簡單。由於我們的數據庫設置需要幾個環境變量,而筆者不願監視之,這是最快也最粗暴的在本地運行的方法。在雲供應商環境中,筆者會更加合理地設置環境變量。

到此為止,項目順利運行了,就如同在 OSX 或 Linux 系統中一樣。

以上即為我們簡短的在 Windows 10 中配置 Node.js 的教程。npm 中的一些模塊可能不支持 Windows,但是這一情況正在好轉。Windows 擁有許多美觀友好的 GUI 工具,Visual Studio 也是很強大的武器。如果你的團隊願意承擔額外的開銷,這或許是一個可行的選擇。

OneAPM 能幫助您輕鬆鎖定Node.js 應用性能瓶頸,通過強大的Trace 記錄逐層分析,直至鎖定行級問題代碼。以用戶角度展示系統響應速度,以地域和瀏覽器維度統計用戶使用情況。

node.js如何運行

Node.js是一個輕鬆構建快速,可擴展的網絡應用平台建立在Chrome的JavaScript運行。Node.js使用事件驅動,非阻塞I/O模型,使得它重量輕,高效,完美的數據密集型實時應用程序運行在分佈式設備。

在Windows上安裝 Node.js很方便,只需要訪問node.js官網 ,點擊Download鏈接,然後選擇Windows Installer,下載安裝包。下載完成後直接雙擊安裝,和其它一般軟件安裝一樣:

選擇安裝位置:

安裝完成:

到此已經安裝Node.sj完成,以下是Node.js安裝目錄結構:

啟動node:在「開始」–「程序」找到:

直接雙擊node.js

測試一個簡單實例:輸出「Hello,World!」

進入node之後,可以輸入:

console.log(“Hello,World!”);

就會看到命令行里輸出了:Hello,World!

如何利用Node.js 構建分佈式集群

那麼到底是如何實現服務端調用解耦的呢?在實現方案中,我們採用了(Node.js + Protocol Buffers + Zookeeper + RabbitMQ)的組合,從而實現配置集中化管理:

1.Node.js,主要用於開發業務邏輯。

作為天生的異步腳本語言,Node.js 使用事件驅動、 非阻塞I/O模型大大提升了研發效率,非常適合在分佈式設備上運行的數據密集型的實時應用。

我們通過 Fibers庫採用協程的方式來解決Node.js 異步編程匿名回調問題,將異步回調邏輯轉化為同步,同時也滿足了程序員使用同步方法編寫異步程序的情懷。

可參考官方介紹:

2.Protocol Buffers,用於強約束消息定義。

Protocol Buffers一種數據交換的格式,它獨立於語言,獨立於平台。由於它是一種二進制的格式,相比XML和JSON,傳輸效率會更高,可以將它用於分佈式應用之間的數據通信或者異構環境下的數據交換。我們主要將Protocol Buffers用來模版化定義消息結構。

可參考:

3.Zookeeper,實現配置集中管理。

Zookeeper分佈式服務框架是Apache Hadoop 的一個子項目,簡單的說,Zookeeper=文件系統+通知機制。它主要是用來解決分佈式應用中經常遇到的一些數據管理問題,如:統一命名服務、狀態同步服務、集群管理、分佈式應用配置項的管理等。

我們使用ZooKeeper看重的是它不僅支持集群高可用,還支持持久化節點、臨時節點存儲和節點變更監控的特點,主要使用了它提供的命名服務、配置管理和集群管理服務。其中,臨時節點特性用以實現名字服務註冊,節點變更監控實現配置集中管理。

參考:

4.RabbitMQ,實現異構通訊服務間的解耦。

Rabbitmq是一種應用程序對應用程序的通信方法,選擇RabbitMQ的原因在於它可以支持集群高可用、簡單易用、性能出色和完善的管理工具(如:Web ui / Rest API )的特點。

使用Rabbitmq中間件服務端實現解耦,其中主要是利用( Work Queue + Topics Exchange )來實現後端的無縫擴容,並採用Publish/Subscribe + RPC 實現調用解耦,並利用MQ 統一輸入輸出。

參考:

走過的一些坑

最後,總結經驗避免犯同樣的錯,是非常重要的,還有一些技術遺留問題,需要我們自行避開這些坑。以下是我們在構建RPC框架過程中遇到的一些坑:

異步編程效率問題(Fibers) Node.js 內存泄漏問題

在複雜在構建複雜應用的時候,很多地方都可能發生內存泄露,也需要考慮異步編程效率問題。為解決這兩個問題,我們目前主要採取以下三個手段來解決:

a) 框架封裝所有網絡通信,業務方只關注業務邏輯、提高研發效率;

b)通過Fibers 封裝所有異步函數調用轉換為同步方法;

c)謹慎選擇第三方庫。

異步框架中日誌跟蹤

異步程序記錄日誌亂序不利於跟蹤業務邏輯調用路徑。為解決這個問題,我們通過包裝 Fibers 對每一個 Fiber 實例進行編號,在所有日誌輸出中打印 Fiber id 記錄異步調用路徑,並配合跨模塊會話編號實現請求調用跟蹤,以此解決日誌紀錄的無序問題。

RabbitMQ HA 高可用問題

如果需要實現RabbitMQ HA 高可用特性,有兩種途徑可以實現:Server 端 HA 和 Client HA。Server 端的高可用性可使用 LVS 或 HAProxy來實現,Client 端的高可用性也是一種選擇,這樣可以減少架構複雜度和層次依賴。值得注意的是,實現高可用特性時,要記得開啟Queue 高可用配置。

()

RabbitMQ HA 網絡閃斷導致節點分區問題

網絡不穩定導致RabbitMQ HA 網絡閃斷,進而導致節點分區問題。針對這個問題,需要添加對 /api/nodes 進行監控,並及時處理分區問題。

具體的解決方法可參考:

ZooKeeper Session Expired

針對ZooKeeper 會話過期問題,需要大家特別關注處理Zookeeper 集群斷開後的重連處理,因為如果重連邏輯沒有處理好的話,所有依賴ZooKeeper的特性都將不可用。

具體解決方法可參考:

結 語

經過應用實踐,目前看來 Node.js幾乎可以做到其他後端語言所能做到所有的事情,ES6特性正式發佈如今有人已經開始高喊「javascript: The World’s Best Programming Language」,但我也並不認為整個後端完全用Node.js來實現會是一個很好的方案。

本文中提到了Node.js的諸多優點,如異步、非阻塞和事件驅動等,但其也存在一些缺點,如默認單進程單線程不能利用多核,腳本弱類型容易出現運行時BUG,同時因為它簡單易用,也導致了代碼質量不易控制,對開發人員也提出了更高的要求。所以,就個人經驗來看,建議偏複雜業務邏輯控制使用Node.js,如果是偏極致性能的業務建議和C++等其他方案結合使用。

原創文章,作者:P3C8R,如若轉載,請註明出處:https://www.506064.com/zh-hk/n/128097.html

(0)
打賞 微信掃一掃 微信掃一掃 支付寶掃一掃 支付寶掃一掃
P3C8R的頭像P3C8R
上一篇 2024-10-03 23:24
下一篇 2024-10-03 23:24

相關推薦

發表回復

登錄後才能評論