这是我正在尝试做的事情:我正在开发一个 Node.js http 服务器,它将保持长连接,以便在一台机器上从数万个移动客户端推送目的(与 redis 协作)。
测试环境:
1.80GHz*2 CPU/2GB RAM/Unbuntu12.04/Node.js 0.8.16
第一次,我使用“express”模块,在使用交换之前我可以达到大约 120k 的并发连接,这意味着 RAM 是不够的。然后,我切换到原生“http”模块,并发量达到了 160k 左右。但是我意识到原生http模块中还有太多我不需要的功能,所以我将它切换到原生“net”模块(这意味着我需要自己处理http协议,但没关系)。现在,我可以达到每台机器大约 250k 的并发连接。
这是我的代码的主要结构:
var net = require('net');
var redis = require('redis');
var pendingClients = {};
var redisClient = redis.createClient(26379, 'localhost');
redisClient.on('message', function (channel, message) {
var client = pendingClients[channel];
if (client) {
client.res.write(message);
}
});
var server = net.createServer(function (socket) {
var buffer = '';
socket.setEncoding('utf-8');
socket.on('data', onData);
function onData(chunk) {
buffer += chunk;
// Parse request data.
// ...
if ('I have got all I need') {
socket.removeListener('data', onData);
var req = {
clientId: 'whatever'
};
var res = new ServerResponse(socket);
server.emit('request', req, res);
}
}
});
server.on('request', function (req, res) {
if (res.socket.destroyed) {
return;
}
pendingClinets[req.clientId] = {
res: res
};
redisClient.subscribe(req.clientId);
res.socket.on('error', function (err) {
console.log(err);
});
res.socket.on('close', function () {
delete pendingClients[req.clientId];
redisClient.unsubscribe(req.clientId);
});
});
server.listen(3000);
function ServerResponse(socket) {
this.socket = socket;
}
ServerResponse.prototype.write = function(data) {
this.socket.write(data);
}
最后,这是我的问题:
如何减少内存使用量以进一步提高并发性?
我真的很困惑如何计算 Node.js 进程的内存使用量。我知道由 Chrome V8 提供支持的 Node.js,有process.memoryUsage() api,它返回三个值:rss/heapTotal/heapUsed,它们之间有什么区别,我应该更关注哪一部分,以及它们的确切组成是什么Node.js 进程使用的内存?
即使我做了一些测试并且似乎没有问题,我也担心内存泄漏。有什么我应该关注的问题或任何建议吗?
我找到了一个关于V8 hidden class的文档,正如它所描述的那样,这是否意味着每当我像上面的代码一样向我的全局对象pendingClients添加一个由clientId命名的属性时,都会生成一个新的隐藏类?剂量是否会导致内存泄漏?
我使用webkit-devtools-agent来分析 Node.js 进程的堆映射。我开始这个过程并拍摄了一个堆快照,然后我向它发送了 10k 个请求并稍后断开它们,之后我再次拍摄了一个堆快照。我使用比较视角来查看这两个快照之间的差异。这是我得到的: 有人可以解释一下吗?(array)/(compiled code)/(string)/Command/Array 的数量和大小增加了很多,这是什么意思?
编辑:我是如何运行加载测试的?
1. 首先,我修改了服务端和客户端的一些参数(要实现60k以上的并发需要多台客户端,因为一台机器最多只有60k+端口(以16位表示))<br> 1.1. 服务器和客户端机器之一,我修改了文件描述符,在将运行测试程序的 shell 中使用这些命令:
ulimit -Hn 999999
ulimit -Sn 999999
1.2. 在服务器机器上,我也修改了一些net/tcp相关的内核参数,最重要的是:
net.ipv4.tcp_mem = 786432 1048576 26777216
net.ipv4.tcp_rmem = 4096 16384 33554432
net.ipv4.tcp_wmem = 4096 16384 33554432
1.3. 至于客户端机器:
net.ipv4.ip_local_port_range = 1024 65535
2. 其次,我使用Node.js编写了一个自定义的模拟客户端程序,因为大多数负载测试工具,ab,siege等都是短连接,但我使用长连接并且有一些特殊要求。
3. 然后我在一台机器上启动服务器程序,在另外三台分开的机器上启动三个客户端程序。
编辑:我确实在单台机器(2GB RAM)上达到了 250k 并发连接,但事实证明,这不是很有意义和实用。因为当连接连接时,我只是让连接挂起,没有别的。当我尝试向他们发送响应时,并发数下降到 150k 左右。正如我计算的那样,每个连接的内存使用量增加了大约 4KB,我猜这与我设置为4096 16384 33554432的net.ipv4.tcp_wmem有关,但即使我将其修改为更小,也没有任何改变。我不知道为什么。
编辑:实际上,现在我对每个 tcp 连接使用多少内存以及单个连接使用的内存的确切组成更感兴趣?根据我的测试数据:
150k 并发消耗大约 1800M RAM(来自free -m输出),Node.js 进程大约有 600M RSS
然后,我假设:
(1800M - 600M) / 150k = 8k,这是单个连接的内核TCP栈内存使用量,它由两部分组成:读缓冲区(4KB)+写缓冲区(4KB)(其实这个和我的设置不符上面的net.ipv4.tcp_rmem和net.ipv4.tcp_wmem,系统如何确定这些缓冲区使用多少内存?)
600M / 150k = 4k,这是Node.js单个连接的内存使用量
我对吗?如何减少这两个方面的内存使用量?
如果有什么地方我没有描述好,请告诉我,我会完善它!任何解释或建议将不胜感激,谢谢!