Node.JS Сбор мусора работает в течение часа?

У меня был предыдущий вопрос с щедростью здесь:

Похоже, что это связано с максимальным объемом памяти VPS, теперь, после увеличения размера памяти VPS до 4 ГБ, node JS потребляет 3,8 ГБ, когда GC, похоже, начинает работать. Затем он принимает ~ 1 час до GC до того, как node снова будет реагировать, по крайней мере, то, что, похоже, смотрит на инструмент мониторинга сервера: Свободная память достигает 0, затем в течение ~ 60 минут выполняется процесс (загрузка ЦП загружается), после чего приложение Node.JS снова отправляет данные.

Является ли такой длинный процесс сбора мусора "нормальным"? Я что-то упускаю?

Вот некоторые графики, чтобы проиллюстрировать это: График 1: Загрузка ЦП 1 Мин, График 2: Сетевой трафик в Мбит/с, График 3: Утилита процессора

! [введите описание изображения здесь] [1]

Для тех, кто не следил за ссылкой выше, эта проблема касается приложения node, которое использует Pub/Sub с Redis для приема сообщений, которые затем отправляются всем подключенным клиентам.

Я прокомментировал "отправку клиентам", и увеличение памяти резко замедляется, заставив меня поверить, что это может быть частично причиной, вот код этой части:

nUseDelay=1;
....
....
if(nUseDelay>0) {
    setInterval(function() {
        Object.getOwnPropertyNames(ablv_last_message).forEach(function(val, idx, array) {
            io.sockets.emit('ablv', ablv_last_message[val]);
        });
        ablv_last_message= {};
    }, 15000*nUseDelay);
}

Если я прокомментирую:

        // Object.getOwnPropertyNames(ablv_last_message).forEach(function(val, idx, array) {
            // io.sockets.emit('ablv', ablv_last_message[val]);
        // });

увеличение памяти кажется очень медленным. Почему это должно быть причиной? Является ли это так называемым "закрытием" и как это было бы идеально перекодировано, если бы так?

Здесь полный код, это не очень сложный процесс работы, он мне больше похож на стандартную структуру для любого такого случая, когда приложение Node.JS отправляет информацию о центральном приложении всем своим подключенных клиентов:

var nVersion="01.05.00";

var nClients=0;
var nUseDelay=1;

var ablv_last_message = [];

// Production
var https = require('https');
var nPort = 6000;               // Port of the Redis Server
var nHost = "123.123.123.123";  // Host that is running the Redis Server
var sPass = "NOT GONNA TELL YA";

var fs = require('fs');
var socketio = require('socket.io');
var redis = require('redis');

//  The server options
var svrPort = 443; // This is the port of service
var svrOptions = {
    key: fs.readFileSync('/etc/ssl/private/key.key'),
    cert: fs.readFileSync('/etc/ssl/private/crt.crt'),
    ca: fs.readFileSync( '/etc/ssl/private/cabundle.crt')
};

// Create a Basic server and response
var servidor = https.createServer( svrOptions , function( req , res ){
  res.writeHead(200);
  res.end('Hi!');
  });

// Create the Socket.io Server over the HTTPS Server
io = socketio.listen( servidor );

// Now listen in the specified Port
servidor.listen( svrPort );

console.log("Listening for REDIS on " + nHost + ":" + nPort);

io.enable('browser client minification');  // send minified client
io.enable('browser client etag');          // apply etag caching logic based on version number
io.enable('browser client gzip');          // gzip the file
io.set('log level', 1);                    // reduce logging

io.set('transports', [
    'websocket'
  , 'flashsocket'
  , 'htmlfile'
  , 'xhr-polling'
  , 'jsonp-polling'
]);

cli_sub = redis.createClient(nPort,nHost);

if(sPass != "") {
  cli_sub.auth(sPass, function() {console.log("Connected!");});
}

cli_sub.subscribe("vcx_ablv");

console.log ("Completed to initialize the server. Listening to messages.");

io.sockets.on('connection', function (socket) {
  nClients++;
  console.log("Number of clients connected " + nClients);
  socket.on('disconnect', function () {
    nClients--;
    console.log("Number of clients remaining " + nClients);
  });
});

cli_sub.on("message",function(channel,message) {
    var oo = JSON.parse(message);
    ablv_last_message[oo[0]["base"]+"_"+oo[0]["alt"]] = message;
});

if(nUseDelay>0) {
    var jj= setInterval(function() {
        Object.getOwnPropertyNames(ablv_last_message).forEach(function(val, idx, array) {
            io.sockets.emit('ablv', ablv_last_message[val]);
        });
        ablv_last_message= {};
    }, 5000*nUseDelay);
}

И вот анализ heapdump после запуска приложения на пару минут: ! [введите описание изображения здесь] [2]

Я думал, что я столкнулся с этим вопросом, так как не было удовлетворительного ответа.

Кстати, я помещал NGINX infront из приложения Node.JS, и все проблемы с памятью исчезли, приложение node теперь располагается примерно на 500 МБ - 1 ГБ.

Ответ 1

Недавно у нас была такая же проблема.

Socket.io v0.9.16 автоматически открывает 5 каналов на соединение и очень затрудняет их закрытие. У нас было 18 серверов, которые постоянно набирали память до тех пор, пока они не замерзнут, и не перезагрузите серверы.

Обновив версию Socket.io v0.9.17, проблема исчезла.

Мы провели неделю или три, просматривая каждую строку кода, чтобы найти виновника.