首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >与Node.js连接时间长,如何减少内存使用和防止内存泄漏?还与V8和webkit相关-devtools

与Node.js连接时间长,如何减少内存使用和防止内存泄漏?还与V8和webkit相关-devtools
EN

Stack Overflow用户
提问于 2012-12-27 03:44:48
回答 2查看 12.1K关注 0票数 48

下面是我要做的事情:我正在开发一台Node.js http服务器,它将在一台机器上从数万个移动客户端进行长时间连接(与redis协作)。

测试环境:

代码语言:javascript
运行
复制
1.80GHz*2 CPU/2GB RAM/Unbuntu12.04/Node.js 0.8.16

在第一次使用“快速”模块时,我可以在交换前达到大约120 K的并发连接,这意味着RAM不够。然后,我切换到本地的"http“模块,并发性达到了160 K。但是我意识到在本机http模块中仍然有太多的功能不需要,所以我将它切换到本机"net“模块(这意味着我需要自己处理http协议,但没关系)。现在,我可以达到大约250 K的并发连接每台机器。

以下是我的代码的主要结构:

代码语言:javascript
运行
复制
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);
}

最后,以下是我的问题:

  1. 如何减少内存使用,从而进一步提高并发性?
  2. 我真的很困惑如何计算Node.js进程的内存使用量。我知道由Chrome驱动的process.memoryUsage() Node.js,它返回三个值: rss/heapTotal/heapUsed,它们之间有什么不同,我应该更多地关注哪个部分,以及Node.js进程使用的内存的确切组成是什么?
  3. 我担心内存泄漏,即使我做了一些测试,而且似乎没有问题。我是否有任何需要关注或建议的地方?
  4. 正如我所描述的,我找到了一个关于pendingClients的文档,这是否意味着每当我向全局对象隐藏类添加一个由clientId命名的属性,就像我上面的代码一样,就会生成一个新的隐藏类吗?它会导致内存泄漏吗?
  5. 我使用webkit-devtools-代理来分析Node.js进程的堆映射。我启动了这个进程并获取了一个堆快照,然后向它发送了10k请求并在稍后断开了它们的连接,之后我又获得了一个堆快照。我使用比较透视图来查看这两个快照之间的差异。我得到的是:

有人能解释一下吗?(数组)/(编译代码)/(字符串)/命令/数组的数量和大小增加了很多,这意味着什么?

编辑:我是如何运行加载测试的?

  1. 首先,我在服务器机和客户端机器上修改了一些参数(为了达到60k以上的并发性,需要多台客户机,因为一台机器最多只有16位的60k+端口)。 1.1。无论是服务器还是客户端机器,我修改了文件描述符,在shell中使用以下命令运行测试程序:
代码语言:javascript
运行
复制
ulimit -Hn 999999
ulimit -Sn 999999

1.2。在服务器机器上,我还修改了一些与net/tcp相关的内核参数,其中最重要的是:

代码语言:javascript
运行
复制
net.ipv4.tcp_mem = 786432 1048576 26777216
net.ipv4.tcp_rmem = 4096 16384 33554432
net.ipv4.tcp_wmem = 4096 16384 33554432

1.3。至于客户端机器:

代码语言:javascript
运行
复制
net.ipv4.ip_local_port_range = 1024 65535
  1. 其次,我使用Node.js编写了一个自定义的模拟客户端程序,因为大多数负载测试工具ab、are等都是用于短连接的,但我使用的是长连接,并且有一些特殊的要求。
  2. 然后我在一台机器上启动了服务器程序,在另外三台分开的机器上启动了三个客户端程序。

编辑:我确实在一台机器上达到了250 k并发连接(2GBRAM),但事实证明,它并不是很有意义和实用性。因为当连接连接时,我只让连接挂起,没有别的。当我试图向他们发送回复时,并发数下降到150 K左右。据我计算,每个连接有大约4KB的内存使用量,我想它与net.ipv4.tcp_wmem有关,我将其设置为4096 16384 33554432,但即使我将其修改为更小,也没有什么变化。我搞不懂为什么。

编辑:实际上,现在我更感兴趣的是每个tcp连接使用了多少内存,以及单个连接所使用的内存的确切组成是什么?根据我的测试数据:

150 k并发性消耗了大约1800百万内存(来自free -m输出),Node.js进程有大约6亿RSS。

然后,我假设:

  • (1800 m-600 m)/150 k= 8k,这是一个连接的内核TCP堆栈内存使用情况,它由两部分组成:读缓冲区(4KB)+写缓冲区(4KB)(实际上,这与我前面设置的net.ipv4.tcp_rmemnet.ipv4.tcp_wmem不匹配,系统如何确定这些缓冲区使用多少内存?)
  • 600 m/150 k= 4k,这是单个连接的Node.js内存使用量

我说的对吗?如何减少这两个方面的内存使用量?

如果有什么地方我没有很好的描述,让我知道,我会完善它!如有任何解释或建议,将不胜感激!

EN

回答 2

Stack Overflow用户

发布于 2012-12-29 18:42:20

  1. 我认为您不应该担心内存使用量的进一步下降。从您包含的读取结果来看,您似乎非常接近可以想象的最低限度(我将其解释为字节,这是未指定单位时的标准)。
  2. 这是一个比我能回答的更深入的问题,但以下是RSS的内容。据我所理解,堆是unix系统中动态分配内存的来源。因此,堆总数似乎是堆上分配给您使用的全部内容,而堆使用的则是您所使用的分配量的多少。
  3. 您的内存使用情况很好,而且看起来您实际上并没有泄漏。我现在还不担心。=]
  4. 我也不知道。
  5. 这个快照似乎是合理的。我认为,从请求激增中创建的一些对象已经被垃圾收集,而其他对象则没有。您可以看到,没有超过10k的对象,而且大多数对象都很小。我说这很好。

不过,更重要的是,我想知道您是如何加载测试的。我曾经尝试过像这样进行大规模的负载测试,而大多数工具根本无法在linux上生成这样的负载,因为开放文件描述符的数量有限(默认情况下每个进程大约有1000个)。同样,一旦使用了套接字,就不能立即再次使用它。据我回忆,这需要一分钟的时间才能再次使用。在此与我通常看到系统打开的文件描述符限制设置在100 K以下这一事实之间,我不确定在未经修改的盒子上是否能够接收到这么多负载,或者在单个框上生成它。由于您没有提到任何这样的步骤,我认为您可能还需要研究负载测试,以确保它正在执行您的想法。

票数 5
EN

Stack Overflow用户

发布于 2013-01-03 20:57:51

只是几个音符:

您需要在对象{ res : res}中包装res吗?您可以直接分配它吗?

代码语言:javascript
运行
复制
pendingClinets[req.clientId] = res;

编辑另一种可能有助于进行微优化的方法

代码语言:javascript
运行
复制
server.emit('request', req, res);

将两个参数传递给“request”,但是您的请求处理程序实际上只需要响应“res”。

代码语言:javascript
运行
复制
res['clientId'] = 'whatever';
server.emit('request', res);

虽然实际数据量保持不变,但在“请求”处理程序参数列表中使用少一个参数将为您节省一个引用指针(几个字节)。但是,当您处理几十万个连接时,几个字节就会加起来。您还将节省处理发出调用上的额外参数的次要cpu开销。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/14049109

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档