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


    处理未捕获的异常

    • 在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
    ', 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.
    ', err.stack);
            res.statusCode = 500;
            res.setHeader('content-type', 'text/plain');
            res.end('Server error.');
          }
        } catch(err){
            console.error('Unable to send 500 response.
    ', 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();
        }); 
      });
    
    });
    
  • 相关阅读:
    May LeetCoding Challenge22 之 比较器comparator、map按Value排成逆序、桶排序
    May LeetCoding Challenge21 之 动态规划的min使用
    May LeetCoding Challenge20 之 二叉树中序遍历
    May LeetCoding Challenge19 之 单调栈2.0
    May LeetCoding Challenge18 之 滑动窗口2.0
    May LeetCoding Challenge17 之 滑动窗口
    May LeetCoding Challenge16 之 链表重组
    APT常用命令
    DDCTF-misc-流量分析
    Wireshark学习笔记
  • 原文地址:https://www.cnblogs.com/jinkspeng/p/4353377.html
Copyright © 2020-2023  润新知