47 votes

De longues connexions avec Node.js, comment réduire l'utilisation de la mémoire et éviter les fuites de mémoire? Aussi en relation avec V8 et webkit-devtools

Voici ce que je suis en train de faire: je suis l'élaboration d'un Node.js serveur http, qui tiendra longtemps connexions pour pousser but(collaboration avec le redis) à partir de dizaines de milliers de clients mobiles en une seule machine.

Environnement de Test:

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

Pour la première fois, j'ai utilisé "express" du module, avec qui j'ai pu atteindre environ 120k connexions simultanées avant de swap utilisé ce qui signifie que la RAM n'est pas assez. Ensuite, je suis passé à la maternelle "http" du module, j'ai eu la simultanéité jusqu'à environ 160k. Mais j'ai réalisé qu'il y a encore beaucoup trop de fonctionnalités je n'ai pas besoin d'en natif http module, donc je suis passé natif "net" module(ce qui signifie que j'ai besoin de gérer le protocole http par moi-même, mais c'est ok). maintenant, je peux atteindre environ 250 connexions simultanées par une seule machine.

Ici est la principale structure de mes codes:

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

Enfin, voici mes questions:

  1. Comment puis-je réduire l'utilisation de la mémoire, de sorte que l'augmentation de la simultanéité plus loin?

  2. Je suis vraiment confus sur la façon de calculer l'utilisation de la mémoire de Node.js processus. Je sais Node.js alimenté par Chrome V8, il est processus.memoryUsage() de l'api et de retour de trois valeurs: rss/heapTotal/heapUsed, quelle est la différence entre eux, la partie de qui dois-je préoccupation de plus, et ce qui est exactement la composition de la mémoire utilisée par le Node.js processus?

  3. Je me suis inquiété de fuite de mémoire, même si j'ai fait quelques tests et il ne semble pas être un problème. Existe-il des points que je doit concerner tout ou conseille?

  4. J'ai trouvé une doc sur les V8 classe caché, comme il le décrit, est-ce à dire chaque fois que j'ajoute une propriété nommée par clientId à mon objet global pendingClients tout comme mes codes ci-dessus, il y aura une nouvelle classe cachée être générée? Dose, il sera la cause de fuite de mémoire?

  5. J'ai utilisé webkit-devtools-agent pour analyser les tas de carte de la Node.js processus. J'ai commencé le processus et a pris un instantané du tas, puis j'ai envoyé 10k demandes et déconnecté plus tard, après que j'ai pris un tas instantané de nouveau. J'ai utilisé la comparaison de la perspective de voir la différence entre ces deux clichés. Voici ce que j'ai: enter image description here Quelqu'un pourrait-il m'expliquer cela? Le nombre et la taille de l' (tableau)/(code compilé)/(string)/Commande/Tableau a beaucoup augmenté, ce qui signifie quoi?

EDIT: Comment ai-je exécuter l'essai de chargement?
1. Tout d'abord, j'ai modifié certains paramètres à la fois sur la machine serveur et les ordinateurs clients(pour atteindre plus de 60k simultanéité besoin de plus d'une machine client, car une machine seulement 60k+ ports(représenté par 16 bits) au plus)
1.1. À la fois le serveur et le client machines, j'ai modifié le fichier descripteur de l'utilisation de ces commandes dans le shell dans lequel le programme sera exécuté dans:

ulimit -Hn 999999
ulimit -Sn 999999

1.2. Sur la machine serveur, j'ai aussi modifié quelques net/tcp liées paramètres du noyau, les plus importants sont:

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

1.3. Comme pour les machines clientes:

net.ipv4.ip_local_port_range = 1024 65535

2. Deuxièmement, j'ai écrit une coutume simuler programme client à l'aide de Node.js puisque la plupart des outils de test de charge, ab, siège, etc, sont à court de connexions, mais je suis sur de longues connexions et ont des besoins particuliers.
3. Puis j'ai commencé le programme serveur sur une seule machine, et trois programme client sur les trois autres machines séparées.

EDIT: J'ai fait parvenir à 250 connexions simultanées sur une même machine(2 go de RAM), mais s'est avéré, il n'est pas très significative et pratique. Parce que lorsqu'une connexion connecté, je viens de laisser la connexion en attente, rien d'autre. Quand j'ai essayé d'envoyé de réponse, la simultanéité nombre est tombé à 150 environ. Comme je l'ai calculé, il y a environ 4 KO de plus l'utilisation de la mémoire par connexion, je suppose que c'est lié à net.ipv4.tcp_wmem que j'ai mis à 4096 16384 33554432, mais même je l'ai modifié pour les petits, rien n'a changé. Je ne peux pas comprendre pourquoi.

EDIT: En fait, maintenant, je suis plus intéressé par la quantité de mémoire utilisée par connexion tcp utilise et ce qui est exactement la composition de la mémoire utilisée par une seule connexion? Selon mes données de test:

150k simultanéité consommé environ 1800M de RAM(de libre -m sortie), et la Node.js le processus a environ 600M RSS

Ensuite, j'ai pris ceci:

  • (1800M - 600M) / 150 = 8k, c'est le noyau de la pile TCP utilisation de la mémoire d'un unique de connexion, il se compose de deux parties: la mémoire tampon de lecture(4 KO) + tampon d'écriture(4 KO)(en Fait, cela ne correspond pas à mon milieu de net.ipv4.tcp_rmem et net.ipv4.tcp_wmem ci-dessus, comment fait-on pour déterminer la quantité de mémoire à utiliser pour ces tampons?)

  • 600M / 150 k = 4k, c'est le Node.js l'utilisation de la mémoire d'une connexion unique

Suis-je le droit? Comment puis-je réduire l'utilisation de la mémoire dans les deux aspects?

Si il y a n'importe où, je n'ai pas décrire le bien, laissez-moi savoir, je vais affiner! Toutes les explications ou des conseils seront les bienvenus, merci!

5voto

tehgeekmeister Points 1180
  1. Je pense que vous ne devez pas vous inquiéter outre la diminution de l'utilisation de la mémoire. À partir de cette lecture, vous avez compris, il semble que vous êtes assez proche pour que le strict minimum envisageable (je l'interpréter comme étant en octets, qui est la norme lorsqu'une unité n'est pas précisée).

  2. C'est un plus en profondeur la question que je peux répondre, mais voici ce que RSS. Le tas est là que la mémoire allouée dynamiquement dans les systèmes unix, du mieux que je comprends. Ainsi, le segment de mémoire totale semble que ce serait tout ce qui est alloué sur le tas pour votre utilisation, tandis que le segment de mémoire utilisée est de savoir comment une grande partie de ce qui est alloué que vous avez utilisés.

  3. Votre utilisation de la mémoire est assez bonne, et il ne semble pas que vous avez une fuite. Je ne m'inquiéterais pas encore. =]

  4. Ne sais pas.

  5. Ce cliché semble raisonnable. Je m'attends à quelques les objets créés à partir de l'afflux de demandes avait été nettoyée, et les autres n'avaient pas. Vous voyez il n'y a rien de plus de 10k objets, et la plupart de ces objets sont assez petites. J'appelle ça de bon.

Plus important encore, cependant, je me demande comment vous êtes en charge des tests. J'ai essayé d'effectuer des tests de charge comme ça avant, et la plupart des outils ne peuvent tout simplement pas réussi à produire le type de charge sur linux, en raison des limites sur le nombre de descripteurs de fichiers ouverts (généralement de l'ordre d'un millier par procédé par défaut). Ainsi, une fois un socket est utilisé, il n'est pas immédiatement disponible pour l'utiliser de nouveau. Il faut une certaine fraction significative d'une minute, je me rappelle que, pour être de nouveau utilisables. Entre cela et le fait que j'ai normalement, vu le large système de descripteur de fichier ouvert limite fixée quelque part en dessous de 100k, je ne suis pas sûr que c'est possible de recevoir autant de charge sur un non modifiée de la boîte, ou de générer sur une seule zone. Puisque vous ne mentionnez pas toutes ces étapes, je pense que vous pourriez aussi avoir besoin de faire de test de charge, assurez-vous que c'est en faisant ce que vous en pensez.

2voto

LastCoder Points 10027

Juste quelques remarques:

Avez-vous besoin d'envelopper res dans un objet {res: res} il vous suffit d'attribuer directement

pendingClinets[req.clientId] = res;

MODIFIER un autre ~micro d'optimisation qui pourrait l'aider

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

passe deux arguments à la demande", mais votre gestionnaire de requêtes vraiment seulement besoin de la réponse 'res'.

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

alors que votre quantité réelle de données reste la même, avoir moins 1 argument de la "demande" des gestionnaires d'arguments, vous fera économiser un pointeur de référence (quelques octets). Mais quelques octets lors du traitement de centaines de milliers de connexions peuvent s'additionner. Vous pourrez également enregistrer le mineur charge de l'unité centrale de traitement de l'argument supplémentaire sur le émettent appel.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X