Comment éviter que node.js ne plante? try-catch ne fonctionne pas


157

D'après mon expérience, un serveur php lèverait une exception au journal ou à la fin du serveur, mais node.js se bloque simplement. Entourer mon code d'un try-catch ne fonctionne pas non plus puisque tout est fait de manière asynchrone. J'aimerais savoir ce que font les autres sur leurs serveurs de production.

Réponses:


132

D'autres réponses sont vraiment insensées, comme vous pouvez les lire dans les propres documents de Node à http://nodejs.org/docs/latest/api/process.html#process_event_uncaughtexception

Si quelqu'un utilise d'autres réponses énoncées, lisez Node Docs:

Notez qu'il uncaughtExceptions'agit d'un mécanisme très grossier pour la gestion des exceptions et peut être supprimé à l'avenir

PM2

Tout d'abord, je recommande fortement l'installation PM2pour Node.js. PM2 est vraiment excellent pour gérer les plantages et surveiller les applications Node ainsi que pour l'équilibrage de charge. PM2 démarre immédiatement l'application Node chaque fois qu'elle se bloque, s'arrête pour une raison quelconque ou même lorsque le serveur redémarre. Ainsi, si un jour, même après avoir géré notre code, l'application tombe en panne, PM2 peut la redémarrer immédiatement. Pour plus d'informations, Installer et exécuter PM2

Revenons maintenant à notre solution pour empêcher l'application elle-même de planter.

Donc, après avoir parcouru, j'ai finalement trouvé ce que le document Node lui-même suggère:

Ne pas utiliser uncaughtException, utiliser domainsavec à la clusterplace. Si vous utilisez uncaughtException, redémarrez votre application après chaque exception non gérée!

DOMAIN avec cluster

En fait, nous envoyons une réponse d'erreur à la demande qui a déclenché l'erreur, tout en laissant les autres terminer dans leur temps normal et en arrêtant d'écouter les nouvelles demandes de ce worker.

De cette manière, l'utilisation du domaine va de pair avec le module de cluster, puisque le processus maître peut créer un fork d'un nouveau worker lorsqu'un worker rencontre une erreur. Voir le code ci-dessous pour comprendre ce que je veux dire

Grâce à l'utilisation Domainet à la résilience de la séparation de notre programme en plusieurs processus de travail Cluster, nous pouvons réagir de manière plus appropriée et gérer les erreurs avec une sécurité beaucoup plus grande.

var cluster = require('cluster');
var PORT = +process.env.PORT || 1337;

if(cluster.isMaster) 
{
   cluster.fork();
   cluster.fork();

   cluster.on('disconnect', function(worker) 
   {
       console.error('disconnect!');
       cluster.fork();
   });
} 
else 
{
    var domain = require('domain');
    var server = require('http').createServer(function(req, res) 
    {
        var d = domain.create();
        d.on('error', function(er) 
        {
            //something unexpected occurred
            console.error('error', er.stack);
            try 
            {
               //make sure we close down within 30 seconds
               var killtimer = setTimeout(function() 
               {
                   process.exit(1);
               }, 30000);
               // But don't keep the process open just for that!
               killtimer.unref();
               //stop taking new requests.
               server.close();
               //Let the master know we're dead.  This will trigger a
               //'disconnect' in the cluster master, and then it will fork
               //a new worker.
               cluster.worker.disconnect();

               //send an error to the request that triggered the problem
               res.statusCode = 500;
               res.setHeader('content-type', 'text/plain');
               res.end('Oops, there was a problem!\n');
           } 
           catch (er2) 
           {
              //oh well, not much we can do at this point.
              console.error('Error sending 500!', er2.stack);
           }
       });
    //Because req and res were created before this domain existed,
    //we need to explicitly add them.
    d.add(req);
    d.add(res);
    //Now run the handler function in the domain.
    d.run(function() 
    {
        //You'd put your fancy application logic here.
        handleRequest(req, res);
    });
  });
  server.listen(PORT);
} 

Bien qu'il Domainsoit en attente de dépréciation et sera supprimé car le nouveau remplacement arrive comme indiqué dans la documentation de Node

Ce module est en attente de dépréciation. Une fois qu'une API de remplacement a été finalisée, ce module sera complètement obsolète. Les utilisateurs qui doivent absolument disposer des fonctionnalités fournies par les domaines peuvent s'y fier pour le moment, mais devraient s'attendre à devoir migrer vers une solution différente à l'avenir.

Mais jusqu'à ce que le nouveau remplacement ne soit pas introduit, Domain with Cluster est la seule bonne solution ce que suggère Node Documentation.

Pour une compréhension Domainet une Clusterlecture approfondies

https://nodejs.org/api/domain.html#domain_domain (Stability: 0 - Deprecated)

https://nodejs.org/api/cluster.html

Merci à @Stanley Luo pour nous avoir partagé cette merveilleuse explication approfondie sur les clusters et les domaines

Cluster et domaines


9
Un mot d'avertissement, le domaine est en attente de dépréciation: lien . La méthode suggérée, à partir de la documentation Node, consiste à utiliser cluster: link .
Paul

4
restart your application after every unhandled exception!Dans le cas où 2000 utilisateurs utilisent un serveur Web de nœud pour le streaming vidéo et 1 utilisateur a une exception, le redémarrage n'interrompra pas tous les autres utilisateurs?
Vikas Bansal

2
@VikasBansal Oui, cela interrompra sûrement tous les utilisateurs et c'est pourquoi il est mauvais à utiliser uncaughtExceptionet à utiliser Domainà la Clusterplace, donc si un utilisateur fait face à une exception, seul son thread est supprimé du cluster et en crée un nouveau pour lui. Et vous n'avez pas non plus besoin de redémarrer votre serveur Node. De l'autre côté, si vous l'utilisez, uncaughtExceptionvous devez redémarrer votre serveur à chaque fois que l'un de vos utilisateurs rencontre un problème. Alors, utilisez Domain with Cluster.
Airy

3
que devons-nous faire quand domainest complètement obsolète et supprimé?
Jas

3
J'ai trouvé ce tutoriel pour ceux qui ne comprennent pas le concept de clusteret workers: sitepoint.com
Stanley Luo

81

J'ai mis ce code juste sous mes instructions require et déclarations globales:

process.on('uncaughtException', function (err) {
  console.error(err);
  console.log("Node NOT Exiting...");
});

travaille pour moi. la seule chose que je n'aime pas à ce sujet, c'est que je ne reçois pas autant d'informations que si je laissais simplement la chose planter.


45
Un mot d'avertissement: cette méthode fonctionne bien, MAIS rappelez-vous que TOUTES les réponses HTTP doivent être terminées correctement. Cela signifie que si une exception non interceptée se produit pendant que vous traitez une requête HTTP, vous devez toujours appeler end () sur l'objet http.ServerResponse. Quelle que soit la manière dont vous implémentez cela, cela dépend de vous. Si vous ne le faites pas, la demande sera suspendue jusqu'à ce que le navigateur abandonne. Si vous avez suffisamment de ces requêtes, le serveur peut manquer de mémoire.
BMiner

3
@BMiner, pourriez-vous fournir une meilleure implémentation? J'ai remarqué ce problème (requête suspendue), donc ce n'est vraiment pas mieux que de simplement redémarrer le serveur en utilisant foreverou quelque chose.
pixelfreak

6
Cela appelle une explication approfondie. Je sais que cela craint, mais chaque fois qu'une exception non interceptée se produit, votre serveur doit redémarrer dès que possible. En réalité, le but de l'événement 'uncaughtException' est de l'utiliser comme une opportunité d'envoyer un e-mail d'avertissement, puis d'utiliser process.exit (1); pour arrêter le serveur. Vous pouvez utiliser pour toujours ou quelque chose comme ça pour redémarrer le serveur. Toutes les demandes HTTP en attente expireront et échoueront. Vos utilisateurs seront en colère contre vous. Mais c'est la meilleure solution. Pourquoi demandes-tu? Checkout stackoverflow.com/questions/8114977/…
BMiner

3
Pour obtenir plus d'informations sur l'erreur non interceptée, utilisez: console.trace (err.stack);
Jesse Dunlap

2
AVERTISSEMENT: La documentation de node dit, en termes non équivoques, que vous ne devriez jamais faire cela car c'est complètement
Jeremy Logan

28

Comme mentionné ici, vous trouverez error.stackun message d'erreur plus complet tel que le numéro de ligne à l'origine de l'erreur:

process.on('uncaughtException', function (error) {
   console.log(error.stack);
});

12

Essayer supervisor

npm install supervisor
supervisor app.js

Ou vous pouvez installer à la foreverplace.

Tout cela va faire est de récupérer votre serveur lorsqu'il plante en le redémarrant.

forever peut être utilisé dans le code pour récupérer gracieusement tout processus qui plante.

Les foreverdocuments contiennent des informations solides sur la gestion des sorties / erreurs par programme.


9
Cela ne peut certainement pas être la solution ... Pendant le temps pendant lequel le serveur est en panne, il ne peut pas répondre aux nouvelles demandes entrantes. Une exception peut être lancée à partir du code de l'application - le serveur doit répondre avec une erreur 500, pas seulement planter et espérer redémarrer.
Ant Kutschera

20
Ainsi, en tant que pirate informatique, on pourrait comprendre qu'ils doivent envoyer une simple requête au serveur et manquer un paramètre de requête - ce qui conduit à un undef dans le javascript qui provoque le crash de node.js. Avec votre suggestion, je peux tuer tout votre cluster à plusieurs reprises. La réponse est de faire échouer l'application avec élégance - c'est-à-dire gérer l'exception non interceptée et ne pas planter. et si le serveur traitait de nombreuses sessions VoIP? il n'est pas acceptable qu'il plante et brûle et que toutes ces sessions existantes meurent avec lui. vos utilisateurs partiraient bientôt.
Ant Kutschera

5
@AntKutschera c'est pourquoi les exceptions devraient être des cas exceptionnels. Les exceptions ne doivent se déclencher que dans les situations où vous ne pouvez pas récupérer et où le processus doit se bloquer. Vous devez utiliser d'autres moyens pour gérer ces cas exceptionnels . Mais je comprends votre point. Vous devriez échouer gracieusement lorsque cela est possible. Il y a cependant des cas où continuer avec un état corrompu fera plus de dégâts.
Raynos

2
Oui, il y a différentes écoles de pensée ici. La façon dont je l'ai appris (Java plutôt que Javascript), il y a des attentes acceptables auxquelles vous devez vous attendre, appelées peut-être des exceptions commerciales, puis il y a des exceptions d'exécution ou des erreurs, où vous ne devriez pas vous attendre à récupérer, comme une mémoire insuffisante. Un problème avec ne pas échouer gracieusement est qu'une bibliothèque que j'écris peut déclarer qu'elle lève une exception dans le cas de quelque chose de récupérable, par exemple où un utilisateur pourrait corriger son entrée. dans votre application, vous ne lisez pas mes documents et vous plantez simplement, où l'utilisateur aurait pu récupérer
Ant Kutschera

1
@AntKutschera C'est pourquoi nous enregistrons les exceptions. Vous devez analyser vos journaux de production pour les exceptions courantes et déterminer si et comment vous pouvez les récupérer, au lieu de laisser le serveur planter. J'ai utilisé cette méthodologie avec PHP, Ruby on Rails et Node. Que vous quittiez ou non un processus, chaque fois que vous générez une erreur 500, vous rendez un mauvais service à vos utilisateurs. Ce n'est pas une pratique spécifique à JavaScript ou à un nœud.
Eric Elliott

7

L'utilisation de try-catch peut résoudre les erreurs non interceptées, mais dans certaines situations complexes, cela ne fonctionnera pas correctement, comme la fonction de capture asynchrone. N'oubliez pas que dans Node, tout appel de fonction asynchrone peut contenir une opération de plantage d'application potentielle.

L'utilisation uncaughtExceptionest une solution de contournement, mais elle est reconnue comme inefficace et sera probablement supprimée dans les futures versions de Node, alors ne comptez pas dessus.

La solution idéale est d'utiliser le domaine: http://nodejs.org/api/domain.html

Pour vous assurer que votre application est opérationnelle même si votre serveur est en panne, procédez comme suit:

  1. utilisez le cluster de nœuds pour créer plusieurs processus par cœur. Donc, si un processus meurt, un autre processus démarre automatiquement. Consultez: http://nodejs.org/api/cluster.html

  2. utilisez domain pour intercepter l'opération asynchrone au lieu d'utiliser try-catch ou uncaught. Je ne dis pas qu'essayer ou non est une mauvaise pensée!

  3. utilisez forever / supervisor pour surveiller vos services

  4. ajoutez un démon pour exécuter votre application de nœud: http://upstart.ubuntu.com

J'espère que cela t'aides!


4

Essayez le module de nœud pm2, il est de loin cohérent et a une excellente documentation. Gestionnaire de processus de production pour les applications Node.js avec un équilibreur de charge intégré. veuillez éviter uncaughtException pour ce problème. https://github.com/Unitech/pm2


`redémarrez votre application après chaque exception non gérée!` Dans le cas où 2000 utilisateurs utilisent un serveur Web de nœud pour la vidéo en streaming et 1 utilisateur a une exception, le redémarrage n'interrompra pas tous les autres utilisateurs?
Vikas Bansal

J'étais si heureux quand j'ai découvert PM2. super logiciel
Mladen Janjetovic

0

UncaughtException est "un mécanisme très grossier" (donc vrai) et les domaines sont désormais obsolètes. Cependant, nous avons encore besoin d'un mécanisme pour détecter les erreurs autour des domaines (logiques). La bibliothèque:

https://github.com/vacuumlabs/yacol

peut vous aider à le faire. Avec un peu d'écriture supplémentaire, vous pouvez avoir une belle sémantique de domaine tout autour de votre code!


0

Fonctionne très bien sur restify:

server.on('uncaughtException', function (req, res, route, err) {
  log.info('******* Begin Error *******\n%s\n*******\n%s\n******* End Error *******', route, err.stack);
  if (!res.headersSent) {
    return res.send(500, {ok: false});
  }
  res.write('\n');
  res.end();
});
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.