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

标签 javascript linux node.js sockets tcp

这是我正在尝试做的事情:我正在开发一个 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协议(protocol),但这没关系)。现在,我可以在每台机器上达到大约 25 万个并发连接。

这是我的代码的主要结构:
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 的文档,正如它所描述的,这是否意味着每当我添加由 命名的属性时客户 ID 到我的全局对象 待处理客户 就像我上面的代码一样,会生成一个新的隐藏类吗?它会导致内存泄漏吗?
  • 我用过 webkit-devtools-agent分析 Node.js 进程的堆图。我开始这个过程并拍摄堆快照,然后我向它发送了 10k 请求并稍后断开它们,之后我再次拍摄了堆快照。我用了 比较透视图来查看这两个快照之间的差异。这是我得到的:
    enter image description here
    有人能解释一下吗? (array)/(compiled code)/(string)/Command/Array的数量和大小增加了很多,这是什么意思?

  • 编辑 :
    我是如何运行加载测试的?
    1.首先修改了server端和client端的一些参数(实现60k以上的并发需要多台client端,因为一台机器最多只有60k+端口(用16位表示))
    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,我猜这与 有关。 net.ipv4.tcp_wmem 我设置为 4096 16384 33554432 ,但即使我将其修改为更小,也没有任何改变。我不明白为什么。

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

    150k concurrency consumed about 1800M RAM(from free -m output), and the Node.js process had about 600M RSS



    然后,我假设:

    • (1800M - 600M) / 150k = 8k, this is the kernel TCP stack memory usage of a single connection, it consists of two parts: read buffer(4KB) + write buffer(4KB)(Actually, this doesn't match my setting of net.ipv4.tcp_rmem and net.ipv4.tcp_wmem above, how does the system determine how much memory to use for these buffers?)

    • 600M / 150k = 4k, this is the Node.js memory usage of a single connection



    我对吗?如何在两个方面减少内存使用?

    如果有什么地方我没有描述好,请告诉我,我会完善它!
    任何解释或建议将不胜感激,谢谢!

    最佳答案

  • 我认为您不必担心进一步减少内存使用量。从您包含的读数来看,您似乎非常接近可以想象的最低限度(我将其解释为以字节为单位,这是未指定单位时的标准)。
  • 这是一个比我能回答的更深入的问题,但这就是 RSS .据我所知,堆是 unix 系统中动态分配内存的来源。因此,堆总数似乎是在堆上为您的使用分配的所有内容,而使用的堆是您已使用的分配量。
  • 你的内存使用情况很好,看起来你实际上没有泄漏。我还不会担心。 =]
  • 不知道。
  • 这个快照似乎是合理的。我预计从大量请求中创建的一些对象已经被垃圾收集了,而另一些则没有。您会看到没有超过 10k 个对象,而且这些对象中的大多数都非常小。我称之为好。

  • 不过,更重要的是,我想知道您如何对此进行负载测试。我以前尝试过像这样进行大规模的负载测试,由于打开文件描述符的数量(通常每个进程大约一千个)的限制,大多数工具根本无法在 linux 上生成这种负载)。同样,一旦使用了套接字,就不能立即再次使用它。我记得,它需要很短的一分钟时间才能再次使用。在此与我通常看到系统范围打开的文件描述符限制设置在 100k 以下这一事实之间,我不确定是否有可能在未修改的盒子上接收那么多负载,或者在单个盒子上生成它。由于您没有提到任何此类步骤,我认为您可能还需要调查您的负载测试,以确保它按照您的想法进行操作。

    关于javascript - 与Node.js的长连接,如何减少内存使用,防止内存泄漏?还与 V8 和 webkit-devtools 相关,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14049109/

    相关文章:

    适用于 ProvideX 数据库的 Linux ODBC 驱动程序

    linux - 我们可以从 Cloudformation 中的 EC2 实例元数据中删除实例用户数据吗

    javascript - 如何将多个回调函数呈现给nodejs中的 View

    php - 在 PHP 应用程序中显示服务器端渲染的 ReactJS 组件

    javascript - 在 Material <md-tab-group> 中使用 Angular 2 组件

    javascript - 尝试使用同一类删除三个 p 元素中字符串末尾的字符

    javascript - 将 C# 正则表达式转换为 JavaScript 正则表达式

    linux - 如果大小超出限制,如何清空连续打印的日志文件?

    javascript - 无法获取 promise 函数的原始返回值

    javascript - 如何使用 Javascript 正确重播任何 flash .swf 文件?