express-16 与生产相关的问题2

平和雅
2023-12-01

处理未捕获的异常

  • 在Node的异步世界中,未捕获的异常是特别需要关注的问题
app.get('/fail', function(req, res){ 
  throw new Error('Nope!');
});
  • 在Express执行路由处理器时,它把它们封装在一个try/catch块中,所以这不是一个真正的未捕获异常。
  • Express会在服务器端记录异常,并且访问者会得到一个丑陋的栈输出。然而服务器是稳定的,其他请求还能得到正确处理。
  • 如果我们想提供一个“好的”错误页面,可以创建文件views/500.handlebars并在所有路由后面添加一个错误处理器:
app.use(function(err, req, res, next){ 
  console.error(err.stack); 
  app.status(500).render('500');
});
  • 提供一个定制的错误页面总归是一个好的做法;比如,可以在这个错误处理器中发送一封邮件给开发团队,让他们知道网站出错了。
//更糟的情况:

app.get('/epic-fail', function(req, res){ 
  process.nextTick(function(){
    throw new Error('Kaboom!'); 
  });
});
  • 它把你的整个服务器都搞垮了。这是因为setTimeout是异步执行的,抛出异常的函数被推迟到Node空闲时才执行。问题是,当Node得到空闲可以执行这个函数时,它已经没有其所服务的请求的上下文了,所以它已经没有资源了,只能毫不客气地关掉整个服务器,因为现在它处于不确定的状态;

  • process.nextTick跟调用没有参数的setTimeout非常像,但它效率更高。我们在这里用它是为了演示,一般你不会在服务器端代码里用它。

  • 可以采取行动处理未捕获的异常,但如果Node不能确定程序的稳定性,也不能。
  • 换句话说,如果出现了未捕获异常,唯一能做的也只是关闭服务器。在这种情况下,最好的做法就是尽可能正常地关闭服务器,并且有个故障转移机制。
  • 最容易的故障转移机制是使用集群。如果你的程序是运行在集群模式下的,当一个工作线程死掉后,主线程会繁衍另一个工作线程来取代它。

关闭服务器

  • Node有两种机制解决这个问题:uncaughtException事件(可能会在将来的Node版本中去掉)和域(推荐)。

  • 一个域基本上是一个执行上下文,它会捕获在其中发生的错误。可以有很多域,可以在处理易出错的代码时创建一个新域。

  • 每个请求都在一个域中处理是一种好的做法,这样就可以追踪那个请求中所有的未捕获错误并做出相应的响应(正常地关闭服务器)。

  • 添加一个中间件就可以非常轻松地满足这个要求。这个中间件应该在所有其他路由或中间件前面

app.use(function(req, res, next){
  // 为这个请求创建一个域
  var domain = require('domain').create();
  // 处理这个域中的错误
  domain.on('error', function(err) {
    console.error('DOMAIN ERROR CAUGHT\n', err.stack);
    try {
      // 在5秒内进行故障保护关机
      setTimeout(function(){ 
          console.error('Failsafe shutdown.');
          process.exit(1);
      }, 5000);

      // 从集群中断开
      var worker = require('cluster').worker; 
      if(worker) worker.disconnect();

      // 停止接收新请求
      server.close();

      try {
        // 尝试使用Express错误路由
        next(err);
      } catch(err) {
        // 如果Express错误路由失效,尝试返回普通文本响应
        console.error('Express error mechanism failed.\n', err.stack);
        res.statusCode = 500;
        res.setHeader('content-type', 'text/plain');
        res.end('Server error.');
      }
    } catch(err){
        console.error('Unable to send 500 response.\n', err.stack);
    }
  });

  // 向域中添加请求和响应对象
  domain.add(req);
  domain.add(res);

  // 执行该域中剩余的请求链
  domain.run(next);
});

// 其他中间件和路由放在这里

var server = http.createServer(app).listen(app.get('port'), function(){
  console.log('Listening on port %d.', app.get('port'));
});
  • 我们做的第一件事是创建一个域,然后在上面附着一个错误处理器。只要这个域中出现未捕获的错误,就会调用这个函数。
  • 我们在这里采取的方式是试图给任何处理中的请求以恰当的响应,然后关闭服务器。
  • 根据错误的性质,可能无法响应处理中的请求,所以我们首先要确立关闭服务器的截止时间。
  • 在这个例子中,我们允许服务器在5秒内响应处理中的请求(如果它可以)。你所选择的数值取决于你的程序,如果程序经常有长请求,就应该给更多的时间。
  • 一旦确立了截止时间,我们会从集群中断开(如果在集群中),以防止集群给我们分配更多的请求。然后明确告诉服务器我们不再接受新的连接。
  • 最后,我们试图传到错误处理路由(next(err))来响应产生错误的请求。如果那会抛出错误,我们退回去用普通的Node API响应。如果其他的全部失败了,我们会记录错误(客户端得不到响应,最终会超时)。
  • 一旦设置好未处理异常处理器,我们就把请求和响应对象添加到域中(允许那些对象上的所有方法抛出的错误都由域处理)。
  • 最后,我们在域的上下文中运行管道中的下一个中间件。注意,这可以有效地运行域中管道里的所有中间件,因为对next()的调用是链起来的。

一篇介绍的文章

用多台服务器扩展

  • 用集群向外扩展可以实现单台服务器的性能最大化, 但当需要多台服务器时会怎样?这时情况会变得有点复杂。要实现这种并行,需要一台代理服务器(为了跟一般用于访问外部网络的代理区别开,经常被称为反向代理或正向代理,但我发现这种叫法既费解又没必要,所以我只称它为代理)。

  • 在代理领域的两个后起之秀分别是Nginx和HAProxy。还有一些比较小的基于Node的代理服务器,比如proxynode-http-proxy
  • 如果要求不高,或者是用于开发,这些都是很好的选择。对于生产环境而言,我推荐用Nginx或HAProxy (这两个都是免费的,尽管提供服务是收费的)。

  • 如果确实配置了一台代理服务器,请确保告知Express你用了代理,并且它应该得到信任:

app.enable('trust proxy');
  • 这样可以确保req.ipreq.protocolreq.secure能反映客户端和代理服务器之间连接的细节,而不是客户端和你的应用之间的。还有,req.ips将会是一个数组,表明原始客户端IP和所有中间代理的名称或IP地址。

网站监控

  • 网站监控是你可以采取的最重要的(也是最常被忽视的) QA措施之一;

第三方正常运行监控

  • 在网站服务器上正常运行一个监控可能可以发现某些页面不能访问,但如果整个服务器都宕掉了,它甚至可能都发不出一个SOS。

  • 一道防线应该是第三方正常运行监控。UptimeRobot有50个免费监控,并且配置简单。警报可以通过邮件、短信(文本消息)、Twitter或者iPhone应用程序发送。
  • 可以监控单个页面的返回码(除200之外的所有返回码都可以视为错误),或者检查页面上有没有某个关键字。不过要记住,如果用关键字监控,它可能会影响你的分析

应用程序故障

  • 正常运行监控可以非常有效地监测大规模故障。如果用关键字监控,它们甚至可以用来监测应用程序故障。
  • 然而,一般在处理故障时都想表现得更优雅。给用户显示一个友好的消息“对不起,这项服务目前不正常”,并且你会收到一封邮件或一条短信告诉你有故障了。
  • 当你依赖第三方组件时,比如数据库或其他Web服务器,一般会采取这种方式。
  • 一种简单的故障处理方式是有错误时给你自己发邮件;如果通知需求复杂,可能要考虑找一个通知服务,比如亚马逊的简单通知服务(SNS)。

压力测试

  • 压力测试是为了相信服务器可以正常地应对成百上千的并发请求; 压力测试可能非常复杂,并且很大程度上取决于你的项目。

  • 先添加一个简单的测试,确保程序可以满足一秒内对主页的100次请求; 用Node模块loadtest做压力测试:

//qa/tests-stress.js:
var loadtest = require('loadtest');
var expect = require('chai').expect; 

suite('Stress tests', function(){
  test('Homepage should handle 100 requests in a second', function(done){ 
    var options = {
        url: 'http://localhost:3000',
        concurrency: 4,
        maxRequests: 100
    };
    loadtest.loadTest(options, function(err,result){
        expect(!err);
        expect(result.totalTimeSeconds < 1);
        done();
    }); 
  });

});

转载于:https://www.cnblogs.com/jinkspeng/p/4353377.html

 类似资料: