警告: 不要忽视错误!

当发生错误时,域(domain)的错误处理程序不是用于代替停歇你的进程。

根据 JavaScript 中 throw 的工作机制,几乎从来没有任何方式可以安全地“从你离开的地方重新拾起(pick up where you left off)”,无泄漏的引用,或创造一些其他种类的不确定脆弱状态。

对抛出的错误响应最安全的方式就是关闭进程。当然,在正常的 Web 服务器中,你可能有许多的连接打开着,因为错误是由别人引起的,就突然关闭进程,这是不合理的。

更好的方法是发送一个错误响应以触发错误的请求,而让其他人在正常的时间内完成,并停止监听该工作进程的新请求。

通过这种方式,domain 的用法是与集群模块携手,因为在工作进程遇到一个错误时,主进程可以产生一个新的工作进程。对于扩展到多台机器的 Node.js 程序而言,在终止代理或服务注册可以记录失败,并作出相应的响应。

例如,这不是一个好主意:

  1. // XXX WARNING! BAD IDEA!
  2. var d = require('domain').create();
  3. d.on('error', (er) => {
  4. // The error won't crash the process, but what it does is worse!
  5. // Though we've prevented abrupt process restarting, we are leaking
  6. // resources like crazy if this ever happens.
  7. // This is no better than process.on('uncaughtException')!
  8. console.log('error, but oh well', er.message);
  9. });
  10. d.run(() => {
  11. require('http').createServer((req, res) => {
  12. handleRequest(req, res);
  13. }).listen(PORT);
  14. });

通过使用域(domain)的上下文和弹性分离我们的程序到多个工作进程中,我们可以做到更合理地响应,并且更安全地处理错误。

  1. // Much better!
  2. const cluster = require('cluster');
  3. const PORT = +process.env.PORT || 1337;
  4. if (cluster.isMaster) {
  5. // In real life, you'd probably use more than just 2 workers,
  6. // and perhaps not put the master and worker in the same file.
  7. //
  8. // You can also of course get a bit fancier about logging, and
  9. // implement whatever custom logic you need to prevent DoS
  10. // attacks and other bad behavior.
  11. //
  12. // See the options in the cluster documentation.
  13. //
  14. // The important thing is that the master does very little,
  15. // increasing our resilience to unexpected errors.
  16. cluster.fork();
  17. cluster.fork();
  18. cluster.on('disconnect', (worker) => {
  19. console.error('disconnect!');
  20. cluster.fork();
  21. });
  22. } else {
  23. // the worker
  24. //
  25. // This is where we put our bugs!
  26. const domain = require('domain');
  27. // See the cluster documentation for more details about using
  28. // worker processes to serve requests. How it works, caveats, etc.
  29. const server = require('http').createServer((req, res) => {
  30. var d = domain.create();
  31. d.on('error', (er) => {
  32. console.error('error', er.stack);
  33. // Note: we're in dangerous territory!
  34. // By definition, something unexpected occurred,
  35. // which we probably didn't want.
  36. // Anything can happen now! Be very careful!
  37. try {
  38. // make sure we close down within 30 seconds
  39. var killtimer = setTimeout(() => {
  40. process.exit(1);
  41. }, 30000);
  42. // But don't keep the process open just for that!
  43. killtimer.unref();
  44. // stop taking new requests.
  45. server.close();
  46. // Let the master know we're dead. This will trigger a
  47. // 'disconnect' in the cluster master, and then it will fork
  48. // a new worker.
  49. cluster.worker.disconnect();
  50. // try to send an error to the request that triggered the problem
  51. res.statusCode = 500;
  52. res.setHeader('content-type', 'text/plain');
  53. res.end('Oops, there was a problem!\n');
  54. } catch (er2) {
  55. // oh well, not much we can do at this point.
  56. console.error('Error sending 500!', er2.stack);
  57. }
  58. });
  59. // Because req and res were created before this domain existed,
  60. // we need to explicitly add them.
  61. // See the explanation of implicit vs explicit binding below.
  62. d.add(req);
  63. d.add(res);
  64. // Now run the handler function in the domain.
  65. d.run(() => {
  66. handleRequest(req, res);
  67. });
  68. });
  69. server.listen(PORT);
  70. }
  71. // This part isn't important. Just an example routing thing.
  72. // You'd put your fancy application logic here.
  73. function handleRequest(req, res) {
  74. switch (req.url) {
  75. case '/error':
  76. // We do some async stuff, and then...
  77. setTimeout(() => {
  78. // Whoops!
  79. flerb.bark();
  80. });
  81. break;
  82. default:
  83. res.end('ok');
  84. }
  85. }