tls连接内存占用

我们有一个 node.js 服务,有大量 tls 长连接,跑了一段时间后发现内存消耗比较大,每个 node.js 实例维持 3000 个连接需要消耗 1G 略多的内存,但是也不能确定有内存泄露,毕竟内存没有再往上涨导致服务中断,所以觉得有必要测试一下 tls 连接的内存消耗情况。

tls服务器

var tls         = require('tls'),
    fs          = require('fs');


var options = {
    key: fs.readFileSync('server.key'),
    cert: fs.readFileSync('server.cert'),
    handshakeTimeout: 10*1000,
    plain: true,
    ssl: true
};

var port = 1234;
var tlsServer = tls.createServer(options, function (socket) {
    socket.on("close", function () {
        console.warn('tls client(' + socket.remoteAddress + ':' + socket.remotePort +') closed');
    });
}).listen(port, "0.0.0.0", function(){
    console.log('tls server listening on port(' + port + ')');
});
tlsServer.maxConnections = 10000;
tlsServer.on('clientError', function (exception, socket) {
    console.warn('tls client(' + socket.remoteAddress + ':' + socket.remotePort +') error(' + exception + ')');
    socket.destroy();
});

function memoryUsage() {
    var mem = process.memoryUsage();
    var format = function(bytes) {
        return (bytes/1024/1024).toFixed(2)+'MB';
    };
    console.log('Process: heapTotal '+format(mem.heapTotal) + ' heapUsed ' + format(mem.heapUsed) + ' rss ' + format(mem.rss));
}

setInterval(function () {
    tlsServer.getConnections(function (err, tlsCount) {
        if (err) {
            console.error("get tls connections count error(" + err.toString() + ")");
        }

        console.warn("server: tls connections(" + tlsCount + ")");

        memoryUsage();
    });
}, 1*1000);

tls客户端

var tls = require('tls');

var tlsCount = 0;

function connect() {
    var socket = tls.connect(1234, {rejectUnauthorized: false}, function () {
        tlsCount += 1;
        socket.setEncoding('utf8');
        socket.on('data', function(data) {
        });
        socket.on('close', function() {
            tlsCount -= 1;
        });
    });
}

function memoryUsage() {
    var mem = process.memoryUsage();
    var format = function(bytes) {
        return (bytes/1024/1024).toFixed(2)+'MB';
    };
    console.log('Process: heapTotal '+format(mem.heapTotal) + ' heapUsed ' + format(mem.heapUsed) + ' rss ' + format(mem.rss));
}

var round = 0;
setInterval(function () {
    if (round < 10) {
        for(var i = 0; i < 100; ++i) {
            connect();
        }
        round += 1;
    }

    console.warn("client: tls connections(" + tlsCount + ")");

    memoryUsage();
}, 1000);

tls服务器日志

连接从 01000 内存增长

server: tls connections(0)
Process: heapTotal 9.14MB heapUsed 4.80MB rss 23.69MB
server: tls connections(100)
Process: heapTotal 10.13MB heapUsed 5.59MB rss 29.53MB
server: tls connections(200)
Process: heapTotal 10.13MB heapUsed 6.75MB rss 32.32MB
server: tls connections(300)
Process: heapTotal 9.18MB heapUsed 7.32MB rss 34.37MB
server: tls connections(400)
Process: heapTotal 10.16MB heapUsed 8.11MB rss 37.05MB
server: tls connections(500)
Process: heapTotal 11.14MB heapUsed 8.66MB rss 38.18MB
server: tls connections(600)
Process: heapTotal 11.14MB heapUsed 9.48MB rss 39.73MB
server: tls connections(600)
Process: heapTotal 12.12MB heapUsed 9.62MB rss 40.50MB
server: tls connections(700)
Process: heapTotal 15.04MB heapUsed 10.16MB rss 42.69MB
server: tls connections(800)
Process: heapTotal 18.97MB heapUsed 10.72MB rss 44.50MB
server: tls connections(900)
Process: heapTotal 18.97MB heapUsed 11.68MB rss 46.30MB
server: tls connections(1000)
Process: heapTotal 18.97MB heapUsed 12.63MB rss 47.32MB

tls客户端结束后连接数从 1000 降到 030 秒后

server: tls connections(0)
Process: heapTotal 22.91MB heapUsed 13.63MB rss 48.44MB

从上面的日志可以计算出:

  • tls 连接 rss 要消耗 24.2KB heap 要消耗 8KB
  • 连接关闭后内存没有释放

    还略有增加,要么就是 node.js 没有释放 tls 连接的内存(应该不太可能),要么就是我的代码有问题,没有释放资源

使用 heapdump 导出堆数据后通过 Chromium 的开发工具进行分析,发现事情没这么简单,占用的内存绝大部分是代码, tls 服务器不退出,使用 tls 客户端测试多轮后可以发现 rss 内存占用不会增加, tls 客户端退出关闭所有连接后, rss 内存占用总是会恢复到同一水平,这就说明代码是没有问题的,应该不存在内存泄露。

多轮测试后重新取样:

server: tls connections(0)
Process: heapTotal 21.93MB heapUsed 13.22MB rss 54.06MB
server: tls connections(100)
Process: heapTotal 21.93MB heapUsed 14.16MB rss 54.06MB
server: tls connections(200)
Process: heapTotal 25.86MB heapUsed 14.79MB rss 58.57MB
server: tls connections(300)
Process: heapTotal 25.86MB heapUsed 15.76MB rss 59.08MB
server: tls connections(400)
Process: heapTotal 25.86MB heapUsed 16.69MB rss 59.08MB
server: tls connections(500)
Process: heapTotal 27.83MB heapUsed 16.72MB rss 60.37MB
server: tls connections(600)
Process: heapTotal 27.83MB heapUsed 17.65MB rss 60.37MB
server: tls connections(700)
Process: heapTotal 33.74MB heapUsed 15.76MB rss 59.32MB
server: tls connections(800)
Process: heapTotal 33.74MB heapUsed 16.71MB rss 59.32MB
server: tls connections(800)
Process: heapTotal 33.74MB heapUsed 16.83MB rss 59.32MB
server: tls connections(900)
Process: heapTotal 33.74MB heapUsed 18.31MB rss 59.38MB
server: tls connections(1000)
Process: heapTotal 33.74MB heapUsed 19.24MB rss 59.38MB

从上面的日志可以计算出:

  • tls 连接 rss 要消耗 5.45KBheap 要消耗 6.16KB

也就是说正常情况下, 3000tls 连接, rss 会消耗 16MBheap 会消耗 18MB ,占用 1G 的问题肯定出在其它地方。

另外注意到调用 heapdump.writeSnapshot 后内存占用大幅下降,应该是 heapdump 触发了 v8 的垃圾回收, v8 的垃圾回收应该不是实时精确的,要精确测量内存占用,估计也要像 heapdump 一样让 v8 将全部垃圾回收后再测量。

不同版本的node.js结果会有差异

为了完全模拟真实环境,针对每一个接收的 tls 连接,除了 close ,还监听了 dataerrortimeout 事件:

socket.on("data", function (data) {

});
socket.on("error", function (err) {
    console.warn('tls client(' + socket.remoteAddress + ':' + socket.remotePort +') error(' + err.toString() + ')');
});
socket.on("close", function () {
    console.warn('tls client(' + socket.remoteAddress + ':' + socket.remotePort +') closed');
});
socket.on("timeout", function () {
    socket.destroy();
});

前面的测试我使用的是 node-v5.3.0 ,我使用 node-v0.12.7 重新测了几轮,最终 rss 内存占用停在 145.5MB ,这还只是 1000tls 连接,使用 3000tls 连接进行测试, rss 内存占用停在 303MB 。再次使用 node-v5.3.0 测试 3000tls 连接, rss 内存占用停在 115MB - 130MB 之间,随着连接的增减有一定的波动。

由此看来 tls 连接是要占用一定内存量的,高版本的 node 改进很大,可以考虑升级一下 node

最后附上 node.js 各个版本的测试结果

Table 1: 10000 tls connections
node.js heapTotal(MB) heapUsed(MB) rss(MB)
node-v0.12.7 158.34 98.62 728.26
node-v0.12.9 239.04 209.53 838.40
node-v4.2.3 86.58 81.47 222.97
node-v5.3.0 96.73 75.23 235.11
  • 测试脚本

    test_tls_server.js

    test_tls_client.js

  • 测试命令

    终端 1 ( tls 服务器)

    sudo bash -c "(ulimit -n 10240; node ./test_tls_server.js)"
    

    终端 2 ( tls 客户端)

    sudo bash -c "(ulimit -n 10240; for (( i = 0; i < 100; i++)); do echo round \$i; node ./test_tls_client.js; done;)"
    

    内存占用取的是 tls 测试客户端退出后,服务器端最后建立了 10000tls 连接时的内存占用。


node