node.js 调用 NATS 与 disque 性能简单对比

比较新的消息队列 server, NATSdisque 的对比。

仅插入

disque

Amount并发 100 500 1000 2000 4000
10w 3568.814ms 1910.550ms 2322.112ms 2577.898ms 2780.189ms
100w 32951.437ms 19610.882ms 20488.052ms 21875.545ms 26354.094ms

NATS

Amount并发 100 500 1000 2000 4000
10w 4486.236ms 4162.363ms 4330.767ms 4458.188ms 4987.342ms
100w 40481.896ms 41818.800ms 41704.926ms 45198.646ms 46906.605ms

双方的插入都在 500 的并发时表现还行,所以后面的测试用这个并发量来插入。

同时插入并读取

测试情况:

插入节点 : Server 节点: 读取节点 = 1: 1: N

disque

10w 级

2327.073ms 11085.363ms
3970.004ms 8064.683ms/8097.301ms
7637.522ms 8798.931ms/8804.586ms/8820.094ms/8986.903ms

100w 级

33974.781ms 128753.262ms
44862.931ms 90111.012ms/90975.795ms
67741.766ms 75597.516ms/75983.190ms/76229.671ms/76412.002ms

NATS

10w 级

6292.183ms 6268.520ms
6172.031ms 3673.333ms/3673.315ms
6127.849ms 2332.848ms/2332.729ms/2338.836ms/2339.670ms

100w 级

55505.581ms 55473.022ms
54234.438ms 29926.494ms/29926.681ms
52572.856ms 16027.231ms/16027.502ms/16025.114ms/16015.165ms

根据数据可以简单看出来, C语言编写的 disque 在纯插入的时候速度确实会快一些。然而到了同时插入并读取的时候, disque 的读取速度就慢了很多,可以看出来 1对多个客户端让 disque 在调度上产生了不小的消耗,而且读取的速度确实也比较慢。

NATS 就比较让人惊喜了,大概是因为异步式的架构,让 NATS 可以不关心插入的确认而是更注重调度和转发,所以让 NATS 的队列读取速度非常快,在跑测试的可以明显的感觉到,在 1:1:4 的时候 NATS 的流程分为 ①插入过程 ②clients读取 ③确认,NATS 的这种架构方式让流程②变得非常迅速。

使用这两个消息队列虽然不会内存泄漏,不过 disque 上限是 1779155,按照博主本机的测试数据看插入的快读取的慢随着时间推移总是有塞满的风险。disque 这种情况大概是因为设计的是安全队列的原因,安全队列类似 Linux 信号中的后 32 个用户自定义信号,不会因为当时没收到就丢失。而 NATS 的队列则属于不安全的队列,如果当时没收到也不会保留该消息,所以 NATS 比较起来塞满的风险更低。

测试环境:

macbook pro
CPU: 2.7 GHz Intel Core i5
MEM: 8 GB 1867 MHz DDR3

测试代码:

node-gnats/req.js
node-gnats/get.js
thunk-disque/req.js
thunk-disque/get.js

PS:
1) 博主这里只测了 1:1:N,没有测 N:1:M 和 N:L:M。
2) 一些比较高级的特性没有开启测试。
如果这些没关注到的这些地方有反转、文中有问题或者有更好的客户端推荐还请评论告知(或者电邮博主 lellansin@gmail.com)。

Advertisements

node.js Mongodb parseError occured 导致连接断开

情况:node.js 使用原生 mongodb 依赖查询。

最近日志收到如下报错:

[2015-12-28 21:00:01.848] [ERROR] console - [Error: parseError occured]
Error: parseError occured
    at null.<anonymous> (/data/game_server_142/node_modules/mongodb/lib/mongodb/connection/connection_pool.js:198:34)
    at emit (events.js:98:17)
    at Socket.<anonymous> (/data/game_server_142/node_modules/mongodb/lib/mongodb/connection/connection.js:411:20)
    at Socket.emit (events.js:95:17)
    at Socket.<anonymous> (_stream_readable.js:764:14)
    at Socket.emit (events.js:92:17)
    at emitReadable_ (_stream_readable.js:426:10)
    at emitReadable (_stream_readable.js:422:5)
    at readableAddChunk (_stream_readable.js:165:9)
    at Socket.Readable.push (_stream_readable.js:127:10)

查询 mongodb 日志如下:

Mon Dec 28 21:00:00 [conn72] query game_142.player query: { ... 查询 ... } nscanned:101 nreturned:101 reslen:1374301 146ms
Mon Dec 28 21:00:01 [conn72] getmore game_142.player query: { ... 查询 ... } cursorid:5517049421632407939 nreturned:427 reslen:4201834 688ms
Mon Dec 28 21:00:01 [conn71] end connection 10.105.50.74:59131
Mon Dec 28 21:00:01 [conn72] SocketException handling request, closing client connection: 9001 socket exception [2] server [10.105.50.74:59132]
Mon Dec 28 21:00:01 [conn74] end connection 10.105.50.74:59134
Mon Dec 28 21:00:01 [conn70] end connection 10.105.50.74:59130
Mon Dec 28 21:00:01 [conn73] end connection 10.105.50.74:59133

报错之后客户端与mongodb之间的连接断开。

谷歌了不少老外的情况看到 http://stackoverflow.com/questions/19546561/node-mongodb-error-connection-closed-due-to-parseerror 这一篇的情况基本与博主碰到的情况相同。

里面提到 “The production Mongo driver throws away all errors in a catch block.” 然后后面说 node 的原生 npm 模块 mongodb 在它的 1.4 版本里面修复了这个问题。

随后检查了下 node_modules/mongodb/package.json 发现版本确实是新的,不过顺着版本思路检查,发现运维新搭的 mongod server 的版本是 2.0.x 而目前公司服务器用的 mongod 版本是 3.0.3,于是升级 mongod 之后解决了。(真是没有一点点防备啊 (╯‵□′)╯︵┻━┻)

ld: library not found for -lgcc_s.10.5 在 Mac 下 NPM 编译安装的常见错误

情况:通过 Node 的 NPM 编译安装某些模块的时候报错:

ld: library not found for -lgcc_s.10.5  
clang: error: linker command failed with exit code 1 (use -v to see invocation)  

解决方案:
到 AppStore 中安装 XCode 7。装完以后,打开 XCode 并且接受 license 协议。更新之后,node-gyp 编译就没有这个问题了。

node.js Error: EBADF, write

最近工作写了个小项目,本以为能好好喝下茶,可是让人想掀桌的报错出现了。

fs.js:77
      throw err;  // Forgot a callback but don't know where? Use NODE_DEBUG=fs
            ^
Error: EBADF, write
    at Error (native)

WTF?! fs.js: 77? 这是要我去看内核源代码?好在项目还不大,拆拆更健康。花了半天的时间之后大概是清楚了。

首先是 Error 的名字 EBADF 其意义是 bad file descriptor 错误的文件描述符。
Error: EBADF, write 表示往错误的文件描述符里面写数据了。

出现这个BUG的场景简而言之,是有一个 .on(‘data’) 事件拿到数据往 fd 里面写,这个时候某个操作抛了 error 我在处理error 的时候 close 了这个 fd,而另外一边去还在触发 data 事件想往这个(已经被我 close 的)fd里面写数据。如下:

// ...

var fd = fs.openSync(path, 'w');

test.on('data', function(data) {
	fs.write(fd, data);
});

test.on('end', function() {
	fs.close(fd);
});

// 在 end 之前 close 就会出现 Error: EBADF, write
setTimeout(function() {
	fs.close(fd);
}, 10);

// ...

解决方案:所以我们排查好出现 fs.close 关闭文件描述符的地方,确保 close 之后不会再有 read/write

Error: EBADF, close

另外附上在谷歌的过程中看到了另外一个类似的错误。这是当你为多种情况做 fs.close(fd); 的处理,然而不幸的是,多个情况被都触发, fs.close(fd) 调用了多遍,同样也会出现 EBADF 错误。这样就能出现:


test.on('end', function() {
	fs.close(fd);
	fs.close(fd); // 多调用了一次就会出现
});

不友好的报错

fs.js:77
      throw err;  // Forgot a callback but don't know where? Use NODE_DEBUG=fs
            ^
Error: EBADF, close
    at Error (native)

解决方案:依旧是排查 fs.close,只不过这次是要保证多种处理不会反复执行 fs.close ,或者你可以使用 try/catch 来无视它。

Error: EBADF, bad file descriptor

最后,当 fd 失效以后进行 read 操作的话,我还以为会出现 Error: EBADF, read 结果并没有。以下是尝试出现BUG的代码:

// ...

fs.closeSync(fd);
fs.readSync(fd, new Buffer(1024), 0, 1024);

// ...

不过这个报错会友好很多,有将其调用栈打出来。

fs.js:552
  var r = binding.read(fd, buffer, offset, length, position);
                  ^
Error: EBADF, bad file descriptor
    at Error (native)
    at Object.fs.readSync (fs.js:552:19)
    at command.<anonymous> (/Users/Lellansin/Documents/workspace/node/test-server/app/services/TestService.js:40:6)
    at command.emit (events.js:110:17)
    at ChildProcess.emit (events.js:129:20)
    at maybeClose (child_process.js:1015:16)
    at Socket.<anonymous> (child_process.js:1183:11)
    at Socket.emit (events.js:107:17)
    at Pipe.close (net.js:485:12)

解决方案:看错误栈去改代码就好了。。

node.js Error: stdout maxBuffer exceeded

在使用 child_process 模块中的 exec 、execFile、spawnSync、execFileSync、execSync 方法时需要注意其 options 参数中的 maxBuffer 项。

以上方法在执行时会在内存中建一个 buffer 来缓冲组合所有的输出数据,而 maxBuffer 则是指定该 buffer 大小的地方。如果输出超过指定的大小则会报 maxBuffer exceeded 的错误。

解决方案是执行的时候估计好大小,设置更大的 maxBuffer:

var exec = require('child_process').exec;

var child = exec('ls -lah', {
	encoding: 'utf8',
	timeout: 0,
	maxBuffer: 5000 * 1024, // 默认 200 * 1024
	killSignal: 'SIGTERM'
}, function(err, stdout, stderr) {
	console.log(stdout);
});

或者是用 spawn 的 .on(‘data’) 事件触发时,手动拼接数据到 .on(‘close’) 事件触发的时候获得完整数据。

node.js 获取中文名长度

// 通过 Node.js 提供的 Buffer 类
var getByBuffer = function(str) {
    var buffer = new Buffer(str);
    var len = buffer.length;
    delete buffer;
    return len;
};

// 通过原生的正则
var getByReg = function(str) {
    var cArr = str.match(/[^x00-xff]/ig);
    return str.length + (cArr == null ? 0 : cArr.length);
};

// 通过原生的字符码
var getByCode = function(str) {
    var realLength = 0,
        len = str.length,
        charCode = -1;
    for (var i = 0; i < len; i++) {
        charCode = str.charCodeAt(i);
        if (charCode >= 0 && charCode <= 128) realLength += 1;
        else realLength += 2;
    }
    return realLength;
};

速度上而言, 使用原生方式的后两者可能会快上 1ms, 编码上来说, 使用 node.js 的 buffer 类可以区别 GBK 和 UTF8, 而原生方法则要自己指定.

node.js express 防盗链模块 anti-leech

原来发了个简单的原理。最近抽空把反盗链的模块好好写了一下。

安装

npm install anti-leech

使用

var express = require('express'),
  path = require('path'),
  app = express();
  
var AntiLeech = require('anti-leech');

// 白名单 { 域名:[ ip, ...] }
var hosts = {
  'localhost': ['127.0.0.1'],
  'localhost:8004': ['*']
};

// 反盗链类型
var exts = ['.png', '.jpg', '.jpeg', '.gif', '.swf', '.flv'];

// 盗链默认指向图片
var pictrue = "/images/default.png";

app.use(AntiLeech({
  allow: hosts,
  exts: exts,
  log: console.log, // 你也可以使用自己的方法来记录
  default: pictrue
}));

// 请在调用静态资源之前先使用反盗链模块
app.use(express.static(path.join(__dirname, 'public')));
app.set('port', process.env.PORT || 8004);

app.get('/', function(req, res) {
  res.redirect("/index.html");
});

app.listen(app.get('port'), function() {
  console.log("Express test server listening on http://localhost:" + app.get('port'));
});



测试内容:localhost:8004 在白名单之中,而 127.0.0.1:8004 不在白名单中,所以 localhost:8004 下访问正常,而 127.0.0.1:8004 下则被重定向到指定的图片。

额,顺便说一句。。上面两张图片我是直接盗链 cnodejs.org 论坛的。。不知道他们会不会用我的反盗链模块诶。