ฉันจะป้องกัน node.js ไม่ให้ทำงานล้มเหลวได้อย่างไร ลองจับไม่ทำงาน


157

จากประสบการณ์ของฉันเซิร์ฟเวอร์ php จะโยนข้อยกเว้นไปยังบันทึกหรือไปยังจุดสิ้นสุดเซิร์ฟเวอร์ แต่ node.js เพียงแค่เกิดปัญหา การล้อมโค้ดของฉันด้วย try-catch นั้นไม่ทำงานเนื่องจากทุกอย่างทำแบบอะซิงโครนัส ฉันต้องการที่จะรู้ว่าสิ่งที่คนอื่นทำในเซิร์ฟเวอร์การผลิตของพวกเขา

คำตอบ:


132

คำตอบอื่น ๆ นั้นบ้ามาก ๆ ที่คุณสามารถอ่านได้ที่เอกสารของ Node ที่http://nodejs.org/docs/latest/api/process.html#process_event_uncaughtexception

หากมีคนใช้คำตอบที่ระบุไว้อื่น ๆ อ่าน Node Docs:

โปรดทราบว่าuncaughtExceptionเป็นกลไกที่หยาบมากสำหรับการจัดการข้อยกเว้นและอาจถูกลบออกในอนาคต

PM2

แรกของทั้งหมดที่ผมอยากจะแนะนำการติดตั้งสำหรับPM2 Node.jsPM2 นั้นยอดเยี่ยมมากในการจัดการความผิดพลาดและการตรวจสอบแอพ Node รวมถึงการปรับสมดุลโหลด PM2 เริ่มต้นแอป Node ทันทีเมื่อใดก็ตามที่เกิดปัญหาหยุดด้วยเหตุผลใดก็ตามหรือแม้กระทั่งเมื่อเซิร์ฟเวอร์รีสตาร์ท ดังนั้นหากสักวันแม้หลังจากจัดการรหัสของเราแล้วแอปขัดข้อง PM2 สามารถเริ่มต้นใหม่ได้ทันที สำหรับข้อมูลเพิ่มเติมการติดตั้งและเรียกใช้ PM2

ตอนนี้กลับมาที่โซลูชันของเราเพื่อป้องกันไม่ให้แอปหยุดทำงาน

ดังนั้นหลังจากผ่านไปในที่สุดฉันก็พบกับสิ่งที่ Node แนะนำ:

อย่าใช้uncaughtExceptionใช้domainsกับclusterแทน หากคุณใช้uncaughtExceptionให้รีสตาร์ทแอปพลิเคชันของคุณหลังจากข้อยกเว้นที่ไม่สามารถจัดการได้ทุกข้อ

DOMAINกับคลัสเตอร์

สิ่งที่เราทำจริง ๆ คือส่งการตอบกลับข้อผิดพลาดไปยังคำขอที่ทริกเกอร์ข้อผิดพลาดในขณะที่ปล่อยให้คนอื่น ๆ เสร็จในเวลาปกติและหยุดฟังคำร้องขอใหม่ในคนงานนั้น

ด้วยวิธีนี้การใช้โดเมนจะไปพร้อมกันกับโมดูลคลัสเตอร์เนื่องจากกระบวนการหลักสามารถแยกผู้ปฏิบัติงานใหม่เมื่อผู้ปฏิบัติงานพบข้อผิดพลาด ดูรหัสด้านล่างเพื่อทำความเข้าใจสิ่งที่ฉันหมายถึง

ด้วยการใช้Domainและความยืดหยุ่นในการแยกโปรแกรมของเราออกเป็นกระบวนการทำงานหลายอย่างโดยใช้Clusterเราสามารถตอบสนองได้อย่างเหมาะสมและจัดการข้อผิดพลาดด้วยความปลอดภัยที่มากขึ้น

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

แม้ว่าDomainจะอยู่ระหว่างการคัดค้านและจะถูกลบออกเนื่องจากการแทนที่ใหม่มาตามที่ระบุในเอกสารของโหนด

โมดูลนี้อยู่ระหว่างการคัดค้าน เมื่อ API การแทนที่ได้รับการสรุปโมดูลนี้จะถูกคัดค้านอย่างสมบูรณ์ ผู้ใช้ที่ต้องมีฟังก์ชั่นที่โดเมนจัดเตรียมไว้อาจใช้งานได้ในขณะนั้น แต่คาดว่าจะต้องย้ายไปใช้โซลูชันอื่นในอนาคต

แต่จนกว่าจะไม่มีการแทนที่ใหม่ Domain with Cluster เป็นทางออกที่ดีเพียงอย่างเดียวที่ Node Documentation แนะนำ

เพื่อความเข้าใจในเชิงลึกDomainและClusterอ่าน

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

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

ขอขอบคุณ @Stanley Luo ที่แบ่งปันคำอธิบายเชิงลึกที่ยอดเยี่ยมเกี่ยวกับคลัสเตอร์และโดเมน

คลัสเตอร์และโดเมน


9
คำเตือน, โดเมนคือการเลิกรอดำเนินการ: การเชื่อมโยง วิธีการแนะนำจากเอกสารโหนดคือการใช้คลัสเตอร์: การเชื่อมโยง
พอล

4
restart your application after every unhandled exception!ในกรณีที่ผู้ใช้ 2,000 รายกำลังใช้โหนดเว็บเซิร์ฟเวอร์สำหรับการสตรีมวิดีโอและผู้ใช้ 1 รายได้รับการยกเว้นจากนั้นการรีสตาร์ทจะไม่รบกวนผู้ใช้รายอื่นทั้งหมดใช่หรือไม่
Vikas Bansal

2
@VikasBansal ใช่ว่าจะแน่นอนขัดจังหวะผู้ใช้ทุกคนและที่ว่าทำไมมันไม่ดีที่จะใช้uncaughtExceptionและการใช้งานDomainที่มีClusterแทนดังนั้นหากผู้ใช้คนหนึ่งใบหน้ายกเว้นเท่านั้นดังนั้นด้ายของเขาจะถูกลบออกจากคลัสเตอร์และสร้างขึ้นใหม่สำหรับเขา และคุณไม่จำเป็นต้องรีสตาร์ทเซิร์ฟเวอร์โหนดเช่นกัน ในขณะที่อีกด้านหนึ่งถ้าคุณใช้uncaughtExceptionคุณต้องรีสตาร์ทเซิร์ฟเวอร์ทุกครั้งที่ผู้ใช้ของคุณประสบปัญหา ดังนั้นใช้โดเมนกับคลัสเตอร์
Airy

3
เราควรทำอย่างไรเมื่อdomainเลิกใช้แล้วและถูกลบออกทั้งหมด?
Jas

3
พบบทช่วยสอนนี้สำหรับผู้ที่ไม่เข้าใจแนวคิดclusterและworkers: sitepoint.com/…
Stanley Luo

81

ฉันใส่รหัสนี้ภายใต้คำสั่งที่ต้องการและการประกาศทั่วโลก:

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

ทำงานได้สำหรับฉัน สิ่งเดียวที่ฉันไม่ชอบเกี่ยวกับมันคือฉันไม่ได้รับข้อมูลมากเท่ากับที่ฉันต้องการหากฉันปล่อยให้สิ่งผิดพลาด


45
คำเตือน: วิธีนี้ใช้ได้ดี แต่โปรดจำไว้ว่าการตอบสนอง HTTP ทั้งหมดจะต้องจบลงอย่างถูกต้อง นั่นหมายความว่าหากมีข้อยกเว้นที่ไม่ได้ตรวจสอบเกิดขึ้นในขณะที่คุณจัดการคำขอ HTTP คุณจะต้องยังคงวางสาย () บนวัตถุ http.ServerResponse อย่างไรก็ตามคุณใช้สิ่งนี้ขึ้นอยู่กับคุณ หากคุณไม่ทำเช่นนี้คำขอจะหยุดจนกว่าเบราว์เซอร์จะยกเลิก หากคุณมีคำขอเหล่านี้เพียงพอเซิร์ฟเวอร์อาจมีหน่วยความจำไม่เพียงพอ
BMiner

3
@BMiner คุณช่วยให้การใช้งานดีขึ้นได้อย่างไร ฉันสังเกตเห็นปัญหานี้ (คำขอหยุดทำงาน) ดังนั้นจึงไม่ดีไปกว่าการรีสตาร์ทเซิร์ฟเวอร์โดยใช้foreverหรืออะไรบางอย่าง
pixelfreak

6
สิ่งนี้เรียกร้องให้มีคำอธิบายในเชิงลึก ฉันรู้ว่านี่แย่มาก แต่เมื่อใดก็ตามที่มีข้อยกเว้นที่ไม่สามารถตรวจจับได้เกิดขึ้นเซิร์ฟเวอร์ของคุณต้องรีบูตโดยเร็ว จริงๆแล้ววัตถุประสงค์ของเหตุการณ์ 'uncaughtException' คือการใช้มันเป็นโอกาสในการส่งอีเมลคำเตือนแล้วใช้ process.exit (1); เพื่อปิดเซิร์ฟเวอร์ คุณสามารถใช้ตลอดไปหรือบางสิ่งเช่นนั้นเพื่อรีสตาร์ทเซิร์ฟเวอร์ คำขอ HTTP ใด ๆ ที่รอดำเนินการจะหมดเวลาและล้มเหลว ผู้ใช้ของคุณจะโกรธคุณ แต่มันเป็นทางออกที่ดีที่สุด คุณถามทำไม? ชำระเงินstackoverflow.com/questions/8114977/…
BMiner

3
ในการรับข้อมูลเพิ่มเติมจากข้อผิดพลาดที่ไม่ได้ตรวจสอบให้ใช้: console.trace (err.stack);
Jesse Dunlap

2
คำเตือน: เอกสารสำหรับโหนดบอกว่าไม่มีเงื่อนไขที่ไม่แน่นอนคุณไม่ควรทำสิ่งนี้เพราะมันอันตรายอย่างยิ่ง: nodejs.org/api/process.html#process_event_uncaughtexception
Jeremy Logan


12

ลอง supervisor

npm install supervisor
supervisor app.js

หรือคุณสามารถติดตั้งforeverแทน

ทั้งหมดนี้จะเป็นการกู้คืนเซิร์ฟเวอร์ของคุณเมื่อเกิดข้อผิดพลาดโดยเริ่มต้นใหม่

forever สามารถใช้ภายในรหัสเพื่อกู้คืนกระบวนการที่ผิดพลาดได้อย่างงดงาม

foreverเอกสารมีข้อมูลที่เป็นของแข็งเมื่อออก / จัดการข้อผิดพลาดทางโปรแกรม


9
แน่นอนว่านี่ไม่ใช่วิธีแก้ปัญหา ... ในช่วงเวลาที่เซิร์ฟเวอร์ล่มจะไม่สามารถตอบสนองต่อคำขอใหม่ที่เข้ามาได้ อาจมีข้อยกเว้นเกิดขึ้นจากรหัสแอปพลิเคชัน - เซิร์ฟเวอร์ต้องตอบกลับด้วยข้อผิดพลาด 500 ไม่ใช่เพียงแค่หยุดทำงานและหวังว่าจะเริ่มใหม่
Ant Kutschera

20
ดังนั้นในฐานะที่เป็นแฮ็กเกอร์เราสามารถคิดได้ว่าพวกเขาจำเป็นต้องส่งคำร้องของ่าย ๆ ไปยังเซิร์ฟเวอร์และพลาดพารามิเตอร์คำขอ - ซึ่งนำไปสู่การยกเลิก undef ในจาวาสคริปต์ซึ่งทำให้ node.js พัง ด้วยข้อเสนอแนะของคุณฉันสามารถฆ่าทั้งกลุ่มของคุณซ้ำแล้วซ้ำอีก คำตอบคือทำให้แอปพลิเคชันล้มเหลวอย่างงดงาม - นั่นคือจัดการข้อยกเว้นที่ไม่ได้ตรวจสอบและไม่ทำงานผิดพลาด เกิดอะไรขึ้นถ้าเซิร์ฟเวอร์จัดการเซสชัน voip มากมาย มันไม่ได้เป็นที่ยอมรับสำหรับมันที่จะผิดพลาดและการเผาไหม้และสำหรับช่วงที่มีอยู่ทั้งหมดเหล่านั้นจะตายไป ผู้ใช้ของคุณจะออกจากในไม่ช้า
Ant Kutschera

5
@ AntKutschera นั่นเป็นเหตุผลที่ข้อยกเว้นควรเป็นกรณีพิเศษ ยกเว้นเพียงอย่างเดียวควรจะยิงในสถานการณ์ที่คุณไม่สามารถกู้คืนและการที่กระบวนการมีความผิดพลาด คุณควรใช้วิธีการอื่นเพื่อจัดการกรณีพิเศษเหล่านี้ แต่ฉันเห็นจุดของคุณ คุณควรล้มเหลวอย่างงดงามที่สุดเท่าที่จะทำได้ อย่างไรก็ตามมีกรณีที่การดำเนินการกับสถานะที่เสียหายจะทำให้เกิดความเสียหายมากขึ้น
Raynos

2
ใช่มีโรงเรียนแห่งความคิดต่าง ๆ อยู่ที่นี่ วิธีที่ฉันเรียนรู้ (Java มากกว่า Javascript) มีความคาดหวังที่ยอมรับได้ซึ่งคุณควรคาดหวังหรือที่รู้จักกันว่าอาจเป็นข้อยกเว้นทางธุรกิจและจากนั้นก็มีข้อยกเว้นหรือข้อผิดพลาดรันไทม์ที่คุณไม่ควรคาดหวัง ปัญหาหนึ่งที่ไม่ล้มเหลวอย่างสง่างามคือห้องสมุดที่ฉันเขียนอาจประกาศว่ามันมีข้อยกเว้นในกรณีที่มีสิ่งที่สามารถกู้คืนได้บอกผู้ใช้สามารถแก้ไขอินพุตของพวกเขาได้ ในแอปของคุณคุณไม่ได้อ่านเอกสารของฉันและเพิ่งพังซึ่งผู้ใช้อาจกู้คืน ableto ได้
Ant Kutschera

1
@AntKutschera นี่คือเหตุผลที่เราบันทึกข้อยกเว้น คุณควรวิเคราะห์บันทึกการใช้งานจริงของคุณเพื่อหาข้อยกเว้นทั่วไปและหาว่าคุณสามารถกู้คืนจากพวกเขาได้อย่างไรและแทนที่จะปล่อยให้เซิร์ฟเวอร์พัง ฉันใช้วิธีการนั้นกับ PHP, Ruby on Rails และ Node ไม่ว่าคุณจะออกจากกระบวนการหรือไม่ทุกครั้งที่คุณมีข้อผิดพลาด 500 ครั้งคุณกำลังทำให้ผู้ใช้เกิดความเสียหาย นี่ไม่ใช่ JavaScript หรือแบบฝึกหัดเฉพาะของ Node
Eric Elliott

7

การใช้ try-catch อาจช่วยแก้ไขข้อผิดพลาดที่ไม่ได้ตรวจจับได้ แต่ในบางสถานการณ์ที่ซับซ้อนมันจะไม่ทำงานเช่นการจับฟังก์ชั่น async โปรดจำไว้ว่าในโหนดการเรียกใช้ฟังก์ชั่น async ใด ๆ อาจมีการทำงานที่ผิดพลาดของแอพ

การใช้uncaughtExceptionเป็นวิธีการแก้ปัญหา แต่ได้รับการยอมรับว่าไม่มีประสิทธิภาพและมีแนวโน้มที่จะถูกลบออกใน Node เวอร์ชันในอนาคตดังนั้นอย่านับรวมไว้

ทางออกที่ดีคือการใช้โดเมน: http://nodejs.org/api/domain.html

เพื่อให้แน่ใจว่าแอปของคุณทำงานและใช้งานได้แม้กระทั่งเซิร์ฟเวอร์ของคุณล้มเหลวให้ใช้ขั้นตอนต่อไปนี้:

  1. ใช้โหนดคลัสเตอร์เพื่อแยกหลาย ๆ กระบวนการต่อหนึ่งคอร์ ดังนั้นหากหนึ่งกระบวนการตายไปกระบวนการอื่นก็จะทำการบูทอัตโนมัติ ลองดู: http://nodejs.org/api/cluster.html

  2. ใช้โดเมนเพื่อตรวจจับการทำงานของ async แทนที่จะใช้ try-catch หรือ uncaught ฉันไม่ได้บอกว่าลองจับหรือไม่ถูกจับเป็นความคิดที่ไม่ดี!

  3. ใช้ตลอดไป / หัวหน้างานในการตรวจสอบบริการของคุณ

  4. เพิ่ม daemon เพื่อรันแอปโหนดของคุณ: http://upstart.ubuntu.com

หวังว่านี่จะช่วยได้!


4

ลองใช้โมดูลโหนด pm2 ซึ่งมีความสอดคล้องและมีเอกสารประกอบที่ยอดเยี่ยม ผู้จัดการกระบวนการผลิตสำหรับแอพ Node.js ที่มี load balancer ในตัว โปรดหลีกเลี่ยง uncaughtException สำหรับปัญหานี้ https://github.com/Unitech/pm2


`รีสตาร์ทแอปพลิเคชันของคุณหลังจากข้อยกเว้นที่ไม่สามารถจัดการได้ทั้งหมด! 'ในกรณีที่ผู้ใช้ 2,000 รายกำลังใช้โหนดเว็บเซิร์ฟเวอร์สำหรับการสตรีมวิดีโอและผู้ใช้ 1 รายได้รับการยกเว้นจากนั้นการรีสตาร์ทจะไม่รบกวนผู้ใช้อื่น
Vikas Bansal

ฉันมีความสุขมากเมื่อฉันค้นพบ PM2 ซอฟต์แวร์ที่ดีเยี่ยม
Mladen Janjetovic

0

UncaughtException คือ "กลไกที่หยาบมาก" (จริงดังนั้น) และโดเมนเลิกใช้แล้วในขณะนี้ อย่างไรก็ตามเรายังต้องการกลไกในการตรวจจับข้อผิดพลาดรอบ ๆ (ตรรกะ) โดเมน ห้องสมุด:

https://github.com/vacuumlabs/yacol

สามารถช่วยคุณทำสิ่งนี้ได้ ด้วยการเขียนเพิ่มเติมเล็กน้อยคุณสามารถมีความหมายของโดเมนที่ดีรอบรหัสของคุณ!


0

ใช้งานได้ดีในการปรับปรุง:

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();
});
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.