本文目录一览:
- 1、node.js 基础操作
- 2、如何编写 Node.js 扩展
- 3、如何在 Windows 10 中搭建 Node.js 环境
- 4、node.js如何运行
- 5、如何利用Node.js 构建分布式集群
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++等其他方案结合使用。