Maison >interface Web >js tutoriel >Une brève analyse du problème de fuite de mémoire dans Node.js_node.js

Une brève analyse du problème de fuite de mémoire dans Node.js_node.js

WBOYWBOYWBOYWBOYWBOYWBOYWBOYWBOYWBOYWBOYWBOYWBOYWB
WBOYWBOYWBOYWBOYWBOYWBOYWBOYWBOYWBOYWBOYWBOYWBOYWBoriginal
2016-05-16 15:53:191155parcourir

Cet article est le premier d'une série d'articles sur la période des fêtes de Node.JS présentés par l'équipe Identity de Mozilla, qui a publié la première version bêta de Persona le mois dernier. Lors du développement de Persona, nous avons construit une série d'outils, du débogage à la localisation, en passant par la gestion des dépendances et bien plus encore. Dans cette série d'articles, nous partagerons nos expériences et ces outils avec la communauté, qui seront utiles à tous ceux qui souhaitent créer un service à haute disponibilité avec Node.js. Nous espérons que vous apprécierez ces articles et sommes impatients de connaître vos réflexions et vos contributions.

Nous commencerons par un article d'actualité sur un problème de fond dans Node.js : les fuites de mémoire. Nous présenterons node-memwatch, une bibliothèque qui permet de rechercher et d'isoler les fuites de mémoire dans Node.


Pourquoi cherchez-vous des ennuis ?

La question la plus fréquemment posée concernant le suivi des fuites de mémoire est : "Pourquoi vous embêter ?". N’y a-t-il pas des problèmes plus urgents qui doivent être résolus en premier ? Pourquoi ne pas choisir de redémarrer le service de temps en temps, ou de lui allouer plus de RAM ? Pour répondre à ces questions, nous vous proposons les trois suggestions suivantes :

1. Peut-être que vous ne vous souciez pas de l'empreinte mémoire croissante, mais le V8 si (V8 est le moteur d'exécution de Node). À mesure que les fuites de mémoire augmentent, la V8 devient plus agressive avec le garbage collector, ce qui peut ralentir l'exécution de votre application. Par conséquent, sur Node, les fuites de mémoire nuiront aux performances du programme.

2. Les fuites de mémoire peuvent déclencher d’autres types de pannes. Le code qui perd de la mémoire peut continuellement faire référence à des ressources limitées. Vous pourriez manquer de descripteurs de fichiers ; vous pourriez également être soudainement incapable d'établir de nouvelles connexions à la base de données. Ce type de problème peut apparaître bien avant que votre application ne manque de mémoire, mais il peut quand même vous causer des ennuis.

3. Finalement, votre application plantera tôt ou tard, et cela se produira certainement à mesure que votre application gagnera en popularité. Tout le monde se moquera de vous et vous ridiculisera sur Hacker News, ce qui fera de vous une tragédie.

Où est le nid de fourmis qui a brisé le remblai sur des milliers de kilomètres ?

Lors de la création d'applications complexes, des fuites de mémoire peuvent se produire à de nombreux endroits. Les fermetures sont probablement les plus connues et les plus tristement célèbres. Étant donné que les fermetures conservent des références à des éléments relevant de leur portée, c'est de là que proviennent généralement les fuites de mémoire.

Les fuites de fermeture ne sont souvent découvertes que lorsque quelqu'un les recherche. Mais dans le monde asynchrone de Node, nous générons constamment des fermetures via des fonctions de rappel à tout moment et en tout lieu. Si ces fonctions de rappel ne sont pas utilisées immédiatement après la création, la mémoire allouée continuera de croître et le code qui ne semble pas présenter de fuites de mémoire fuira. Et ce genre de problème est plus difficile à trouver.

Votre application peut également provoquer des fuites de mémoire en raison de problèmes dans le code en amont. Peut-être pouvez-vous localiser le code à l'origine de la fuite de mémoire, mais vous pouvez simplement regarder votre code parfait et vous demander comment il a fui !


Ce sont ces fuites de mémoire difficiles à localiser qui nous donnent envie d'un outil comme node-memwatch. La légende raconte qu'il y a quelques mois, notre propre Lloyd Hilaiel s'est enfermé dans une petite pièce pendant deux jours, essayant de retrouver une fuite de mémoire révélée lors de tests de résistance. (Au fait, restez à l’écoute du prochain article de Lloyd sur les tests de charge)

Après deux jours de travail acharné, il a finalement découvert le coupable dans le noyau Node : l'écouteur d'événements dans http.ClientRequest n'a pas été publié. (Le patch qui a finalement résolu le problème ne comportait que deux lettres cruciales). C'est cette expérience douloureuse qui a incité Lloyd à écrire un outil capable d'aider à détecter les fuites de mémoire.

Outil de localisation de fuite de mémoire

Il existe de nombreux outils utiles et en constante amélioration pour localiser les fuites de mémoire dans les applications Node.js. En voici quelques-uns :

  • node-mtrace de Jimb Esser, qui utilise l'outil mtrace de GCC pour analyser l'utilisation du tas.
  • Le node-heap-dump de Dave Pacheco prend un instantané du tas V8 et sérialise le tout dans un énorme fichier JSON. Il contient également des outils JavaScript pour analyser les résultats instantanés des études.
  • Le v8-profiler et le node-inspector de Danny Coates fournissent un profileur V8 intégré à Node et une interface de débogage basée sur WebKit Web Inspector.
  • Celui de Felix Gnass ne désactive pas la branche keeper chart .
  • Le Tutoriel sur les fuites de mémoire de nœud de Felix Geisendorfer est un didacticiel court et intéressant sur la façon d'utiliser le profil v8 et le débogueur de nœud. Il s’agit également du guide technique de débogage des fuites de mémoire Node.js le plus avancé.
  • La plateforme SmartOS de Joyent, qui fournit un grand nombre d'outils pour déboguer les fuites de mémoire Node.js.

Nous aimons tous les outils ci-dessus, mais aucun d'entre eux ne s'applique à notre scénario. Web Inspector est idéal pour développer des applications, mais difficile à utiliser dans des scénarios de déploiement à chaud, en particulier lorsque plusieurs serveurs et sous-processus sont impliqués. De même, les fuites de mémoire qui se produisent lors d’opérations à charge élevée à long terme sont également difficiles à reproduire. Des outils comme dtrace et libumem, bien qu'impressionnants, ne sont pas disponibles sur tous les systèmes d'exploitation.

Enternode-memwatch

Nous avons besoin d'une bibliothèque de débogage multiplateforme qui n'exige pas que l'appareil nous indique quand notre programme peut avoir une fuite de mémoire et qui nous aide à trouver où la fuite existe. Nous avons donc implémenté node-memwatch.

Cela nous apporte trois choses :

Un émetteur d'événement 'fuite'
 

   memwatch.on('leak', function(info) {
  // look at info to find out about what might be leaking
  });

Un 'émetteur d'événement de statut

       

  var memwatch = require('memwatch');
  memwatch.on('stats', function(stats) {
  // do something with post-gc memory usage stats
  });

Une classification des zones de mémoire tas

  var hd = new memwatch.HeapDiff();
  // your code here ...
  var diff = hd.end();

Et il existe également une fonction qui peut déclencher le garbage collector qui est très utile lors des tests. D'accord, quatre au total.
 

 var stats = memwatch.gc();

memwatch.on('stats', ...) : Statistiques du tas post-GC

node-memwatch peut émettre un échantillon d'utilisation de la mémoire après une récupération complète et un compactage de la mémoire avant qu'un objet JS ne soit alloué. (Il utilise le hook post-gc de la V8, V8::AddGCEpilogueCallback, pour collecter des informations sur l'utilisation du tas à chaque fois qu'un garbage collection est déclenché)

Les statistiques incluent :

  • usage_trend (tendance d'utilisation)
  • current_base (base actuelle)
  • estimate_base (base attendue)
  • num_full_gc (nombre de collectes de déchets complètes)
  • num_inc_gc (augmentation du nombre de garbage collection)
  • heap_compactions (nombre de compressions mémoire)
  • min (minimum)
  • maximum (maximum)

Voici un exemple de ce à quoi ressemblent les données d'une application présentant une fuite de mémoire. Le graphique ci-dessous suit l'utilisation de la mémoire au fil du temps. La ligne verte folle montre ce que process.memoryUsage() rapporte. La ligne rouge montre la base_actuelle signalée par node_memwatch. La case en bas à gauche affiche des informations supplémentaires.

2015623152204606.png (572×441)

Notez que les Incr GC sont très élevés. Cela signifie que le V8 essaie désespérément d'effacer la mémoire.

memwatch.on('leak', ...) : Tendance d'allocation du tas

Nous avons défini un algorithme de détection simple pour vous alerter que votre application peut avoir une fuite mémoire. Autrement dit, si après cinq GC consécutifs, la mémoire est toujours allouée mais n'est pas libérée, node-memwatch émettra un événement de fuite. Le format d'information spécifique à l'événement est clair et facile à lire, comme ceci :

{ start: Fri, 29 Jun 2012 14:12:13 GMT,
 end: Fri, 29 Jun 2012 14:12:33 GMT,
 growth: 67984,
 reason: 'heap growth over 5 consecutive GCs (20s) - 11.67 mb/hr' }

memwatch.HeapDiff(): 查找泄漏元凶

最后,node-memwatch能比较堆上对象的名称和分配数量的快照,其对比前后的差异可以帮助找出导致内存泄漏的元凶。
 

var hd = new memwatch.HeapDiff();
 
// Your code here ...
 
var diff = hd.end();

对比产生的内容就像这样:
 

{
 "before": {
  "nodes": 11625,
  "size_bytes": 1869904,
  "size": "1.78 mb"
 },
 "after": {
  "nodes": 21435,
  "size_bytes": 2119136,
  "size": "2.02 mb"
 },
 "change": {
  "size_bytes": 249232,
  "size": "243.39 kb",
  "freed_nodes": 197,
  "allocated_nodes": 10007,
  "details": [
   {
    "what": "Array",
    "size_bytes": 66688,
    "size": "65.13 kb",
    "+": 4,
    "-": 78
   },
   {
    "what": "Code",
    "size_bytes": -55296,
    "size": "-54 kb",
    "+": 1,
    "-": 57
   },
   {
    "what": "LeakingClass",
    "size_bytes": 239952,
    "size": "234.33 kb",
    "+": 9998,
    "-": 0
   },
   {
    "what": "String",
    "size_bytes": -2120,
    "size": "-2.07 kb",
    "+": 3,
    "-": 62
   }
  ]
 }
}

HeapDiff方法在进行数据采样前会先进行一次完整的垃圾回收,以使得到的数据不会充满太多无用的信息。memwatch的事件处理会忽略掉由HeapDiff触发的垃圾回收事件,所以在stats事件的监听回调函数中你可以安全地调用HeapDiff方法。

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn