javascript Lunghe connessioni con Node.js, come ridurre l'utilizzo della memoria e prevenire perdite di memoria? Anche in relazione con V8 e webkit-devtools




linux sockets (2)

Ecco cosa sto cercando di fare: sto sviluppando un server http Node.js, che conterrà lunghe connessioni per lo scopo di spingere (collaborare con redis) da decine di migliaia di client mobili in una singola macchina.

Ambiente di test:

1.80GHz*2 CPU/2GB RAM/Unbuntu12.04/Node.js 0.8.16

Per la prima volta, ho usato il modulo "express", con il quale potevo raggiungere circa 120k connessioni simultanee prima dello swap in uso, il che significa che la RAM non è sufficiente. Poi, sono passato al modulo "http" nativo, ho ottenuto la concorrenza fino a circa 160k. Ma ho capito che ci sono ancora troppe funzionalità che non ho bisogno nel modulo nativo http, quindi l'ho passato al modulo nativo "net" (questo significa che devo gestire il protocollo http da solo, ma va bene). ora, posso raggiungere circa 250.000 connessioni simultanee per singola macchina.

Ecco la struttura principale dei miei codici:

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);
}

Finalmente, ecco le mie domande:

  1. Come posso ridurre l'utilizzo della memoria in modo da aumentare ulteriormente la concorrenza?

  2. Sono davvero confuso su come calcolare l'utilizzo della memoria del processo Node.js. Conosco Node.js supportato da Chrome V8, c'è process.memoryUsage () api e restituisce tre valori: rss / heapTotal / heapUsed, qual è la differenza tra loro, quale parte dovrei riguardare di più, e qual è la composizione esatta del memoria utilizzata dal processo Node.js?

  3. Mi sono preoccupato per la perdita di memoria anche se ho fatto alcuni test e non sembra esserci un problema. Ci sono dei punti che dovrei riguardare o qualche consiglio?

  4. Ho trovato un documento sulla classe nascosta V8 , come descritto, significa che ogni volta che aggiungo una proprietà chiamata clientId al mio oggetto globale pendingClients, proprio come i miei codici sopra, verrà generata una nuova classe nascosta? Dose causerà perdita di memoria?

  5. Ho usato webkit-devtools-agent per analizzare la mappa dell'heap del processo Node.js. Ho avviato il processo e preso uno snapshot dell'heap, quindi ho inviato richieste 10k e disconnesso in seguito, dopo di che ho recuperato un'istantanea di heap. Ho usato la prospettiva di confronto per vedere la differenza tra queste due istantanee. Ecco cosa ho ottenuto: Qualcuno potrebbe spiegarlo? Il numero e la dimensione di (array) / (codice compilato) / (stringa) / comando / array sono aumentati molto, cosa significa?

EDIT : come ho eseguito il test di caricamento?
1. In primo luogo, ho modificato alcuni parametri sia su macchine server che su macchine client (per ottenere più di 60k di concorrenza è necessario più di un computer client, perché una macchina ha solo 60k + porte (rappresentate da 16 bit) al massimo)
1.1. Sia il server che i computer client, ho modificato il descrittore di file utilizzando questi comandi nella shell in cui verrà eseguito il programma di test:

ulimit -Hn 999999
ulimit -Sn 999999

1.2. Sulla macchina server, ho anche modificato alcuni parametri del kernel relativi a net / tcp, i più importanti sono:

net.ipv4.tcp_mem = 786432 1048576 26777216
net.ipv4.tcp_rmem = 4096 16384 33554432
net.ipv4.tcp_wmem = 4096 16384 33554432

1.3. Per quanto riguarda le macchine client:

net.ipv4.ip_local_port_range = 1024 65535

2. In secondo luogo, ho scritto un programma client simulato personalizzato utilizzando Node.js, poiché la maggior parte degli strumenti di test del carico, ab, assedio, ecc. Sono per connessioni brevi, ma sto usando connessioni lunghe e ho alcuni requisiti speciali.
3. Quindi ho avviato il programma server su una singola macchina e tre programmi client sulle altre tre macchine separate.

EDIT : ho raggiunto 250k connessioni simultanee su una singola macchina (2 GB di RAM), ma ho scoperto che non è molto significativo e pratico. Perché quando una connessione è connessa, ho solo lasciato la connessione in sospeso, nient'altro. Quando ho provato a inviare una risposta, il numero di concorrenza è sceso a 150k. Come ho calcolato, c'è un consumo di memoria 4KB maggiore per connessione, suppongo che sia correlato a net.ipv4.tcp_wmem che ho impostato su 4096 16384 33554432 , ma anche io l'ho modificato in piccolo, nulla è cambiato. Non riesco a capire perché.

EDIT : In realtà, ora sono più interessato a quanta memoria usa la connessione TCP e qual è la composizione esatta della memoria utilizzata da una singola connessione? Secondo i miei dati di test:

La concorrenza 150k ha consumato circa 1800M di RAM (dall'output libero -m ) e il processo Node.js ha avuto circa 600 milioni di RSS

Quindi, ho assunto questo:

  • (1800M - 600M) / 150k = 8k, questo è l'uso della memoria dello stack TCP del kernel di una singola connessione, si compone di due parti: buffer di lettura (4KB) + buffer di scrittura (4KB) (in realtà, questo non corrisponde alla mia impostazione di net.ipv4.tcp_rmem e net.ipv4.tcp_wmem sopra, in che modo il sistema determina la quantità di memoria da utilizzare per questi buffer?)

  • 600M / 150k = 4k, questo è l'utilizzo della memoria Node.js di una singola connessione

Ho ragione? Come posso ridurre l'utilizzo della memoria in entrambi gli aspetti?

Se ci sono ovunque che non ho descritto bene, fammi sapere, lo perfezionerò! Qualsiasi spiegazione o consiglio sarà apprezzato, grazie!


Answer #1

Solo alcune note:

Hai bisogno di racchiudere res in un oggetto {res: res} puoi semplicemente assegnarlo direttamente

pendingClinets[req.clientId] = res;

MODIFICA un'altra ~ micro ottimizzazione che potrebbe aiutare

server.emit('request', req, res);

passa due argomenti a 'richiesta', ma il tuo gestore di richieste ha davvero bisogno solo della risposta 'res'.

res['clientId'] = 'whatever';
server.emit('request', res);

mentre la quantità di dati effettivi rimane la stessa, con 1 argomento in meno nella lista degli argomenti dei gestori di 'richiesta' si salverà un puntatore di riferimento (alcuni byte). Ma alcuni byte quando si elaborano centinaia di migliaia di connessioni possono sommarsi. Si salverà anche il sovraccarico della cpu minore dell'elaborazione dell'argomento extra sulla chiamata emit.


Answer #2
  1. Penso che non dovresti preoccuparti di ridurre ulteriormente l'utilizzo della memoria. Da quella lettura che hai incluso, sembra che tu sia abbastanza vicino al minimo possibile (lo interpreto in byte, che è standard quando non viene specificata un'unità).

  2. Questa è una domanda più approfondita di quella che posso rispondere, ma ecco cosa RSS . L'heap è il punto in cui la memoria allocata dinamicamente proviene da sistemi unix, come meglio comprendo. Quindi, il totale dell'heap sembra essere tutto ciò che è allocato sull'heap per l'utilizzo, mentre lo heap utilizzato è quanto di ciò che è stato assegnato.

  3. L'utilizzo della memoria è abbastanza buono e non sembra che tu abbia effettivamente una perdita. Non mi preoccuperei ancora. =]

  4. Non lo so

  5. Questa istantanea sembra ragionevole. Mi aspetto che alcuni degli oggetti creati dall'ondata di richieste siano stati raccolti dalla spazzatura e altri no. Vedete non c'è niente oltre 10k oggetti, e la maggior parte di questi oggetti sono piuttosto piccoli. Lo chiamo bene.

Ancora più importante, però, mi chiedo come stai testando questo. Ho provato a fare test di carico massivo come questo prima, e molti strumenti semplicemente non riescono a generare quel tipo di carico su linux, a causa dei limiti sul numero di descrittori di file aperti (generalmente circa un migliaio per processo di default ). Inoltre, una volta utilizzato un socket, non è immediatamente disponibile per l'uso. Ci vuole una frazione significativa di un minuto, come ricordo, per essere di nuovo utilizzabile. Tra questo e il fatto che di solito ho visto il limite del descrittore di file aperto del sistema impostato da qualche parte sotto 100k, non sono sicuro che sia possibile ricevere tanto carico su una scatola non modificata, o generarlo su una singola scatola. Dal momento che non menzioni alcuna di tali passaggi, penso che potresti dover anche indagare sui test di carico, per assicurarti che stia facendo ciò che pensi.





tcp