本文章的关键词
- 进程退出
- 内存泄漏
- domain安全保护


Node.js 异常的危害

很多初学的同学很少会关注Node.js的服务安全问题,而当服务器在生成环境遇到此类问题时,又会显得速手无策,因此在学习Node.js的初期了解其常见的危害事关重要。Node.js异常带来的危害主要包含两个方面,一个是来自服务器长期的危害,一个则来自服务的致命危害。

服务器致命危害

这里主要说的是由于代码健壮性问题,常常会由于局部的代码问题,导致整个服务功能异常退出,可简单说明为一个用户的异常请求,会影响到整个服务的功能,一个用户会将整个服务失效。最常见的莫过于代码中包含一些异常逻辑,或者一些隐藏的不常调用的触发逻辑导致的生成环境问题。

长期服务危害

由于Node.js是一个常驻内存的server,因此会由于小部分代码泄漏或者小部分句柄未关闭,当服务器请求累积到一定的程度的时候,就会导致服务器的句柄或者内存达到服务器限制,从而必须重启服务才可继续提供用户服务,而在这重启的过程中,往往会导致一些不可预知的问题。

既然有上面的两种比较严重的危害,那么我们应该在哪些场景中注意这些问题呢?

常见的 Node.js 异常逻辑

这里主要是提出一些可能出现异常逻辑的代码,对于初学者可借鉴,如果你是有经验的童鞋请忽略。主要看下面几段代码逻辑。

Node.js的变量异常

var c = a + b;
var a = {'t' : 1}
console.log(a.t);
console.log(a.w);
console.log(a.w.r);

上面一段代码中console.log(a.w.r);这部分代码就会导致服务器的进程异常退出,首先前面两个console.log都是正常的,即使说a.w为null其实也是不会导致代码异常,但是如果使用null.r的话,因为null对象并非包含r属性,因此这部分会抛出Node.js异常,从而会导致整个服务的异常。
对于这点来说我们最好的处理办法就是在使用变量时候最好做一次属性检查,避免出现null.属性的问题,也就是可以优化为如下方式:

var objArr = [{'test':1}, {'test':2}];
objArr[0]['test'];
objArr[2];
if(typeof objArr[2] == 'object'){
    console.log(objArr[2]['test']);
} else {
    console.log('it is not a object');
}

当然对于对象属性检查也是一样的,如下代码会导致异常

var testErrObject = 'dasdasd';
JSON.stringify(testErrObject);

可优化为

var testObject = {'test' : 1};
if(typeof testObject == 'object'){
    console.log(JSON.stringify(testObject));
} else {
    console.log('it is not a object');
}

var testString = '{"key" : "test is a test"}';
if(typeof testString == 'string'){
    console.log(JSON.parse(testString));
} else {
    console.log('it is not a string');
}

以上就是Node.js的变量异常。

Node.js的函数以及调用异常

函数异常主要是在定义时候,以及调用时候。

未申明

在Node.js中如果函数未申明则调用就会很容易出现异常,但是这种错误一般在开发阶段就会发现。对于这种情况我们常见的是在一个module中定义一个未export的函数,而在其他module中调用才会发生。

函数回调异常

这里主要还是针对Node.js中的异步函数,异步函数都是在异步回调中处理返回结果,但是经常会有同学同步的去获取执行结果,导致一直未得到正确的返回,而有些时候这种错误不会被发现,但是当现网运行时会由于某些用户的操作触发该问题。当然下面的代码就是初学者必须要了解的,一般有所经验的Node.js开发者都不会出现。

var fs = require('fs');
var fileData = fs.readFile('./test.txt', function(err, data){

});
console.log(fileData);

上面就是场景的一些异常问题,既然存在问题,那么我们就想想如何去处理这些异常问题。

保证 Node.js 健壮性的方案

这里主要是针对代码异常做的一个健壮性,对于Node.js内存异常的问题,我们会下节课进行讨论分析。
我们来看看三种常见的方案

常见保护逻辑

这里主要是针对一些低级的变量异常、对象调用异常问题,这部分主要的处理就是在调用前进行相应的检测判断,特别是对于对象和数组调用的时候,避免这种异常错误。

var arr = [1, 2, 3];
var obj = {'1' : '1', '2' : '2', '3' : '3'};

console.log(arr[4]);
console.log(obj[4]);

var objArr = [{'test':1}, {'test':2}];
objArr[0]['test'];
objArr[2];
objArr[2]['test'];

前面也介绍了处理方法,上面是调用异常问题,如果我们在调用test属性时,先进行判断再调用就不会出现严重异常问题,如下:

var objArr = [{'test':1}, {'test':2}];
objArr[0]['test'];
objArr[2];
if(typeof objArr[2] == 'object'){
    console.log(objArr[2]['test']);
} else {
    console.log('it is not a object');
}

通用的保护逻辑try catch

try catch的话可以针对所有非异步执行代码的异常问题,只要是在同步调用函数中出现异常,都可以使用try catch来保护,但是对于异步回调函数中如果出现异常时,外层的try catch就无法捕获,因此如果使用try catch保护就会做的很繁琐。
首先我们看一个try catch保护逻辑的示例:

try{
    callErr();
}catch(err){
    console.log(err);
}

function callErr(){
    var s = wrong + true;
    console.log(someSth);
}

上面一段代码中callErr就是一个同步异常代码,这里使用try catch就可以很好的捕获,并且不会导致服务的异常退出。但是如果是如下示例代码时,就会显得有些无力了。

try{
    callErr();
}catch(err){
    console.log(err);
}

function callErr(){
    setTimeout(function(){
        var s = wrong + true;
        console.log(someSth);
    }, 10);
}

如上代码,如果我们还是希望使用try catch来做保护应该怎么做呢?

try{
    callErr();
}catch(err){
    console.log(err);
}

function callErr(){
    setTimeout(function(){
        try{
            var s = wrong + true;
            console.log(someSth);
        }catch(err){
            console.log(err);
        }
    }, 10);
}

虽然上面代码可以解决问题,但是细心的同学可以看到,如果去处理异常会让人奔溃,那么如何才能优雅的处理这种异步回调中的异常捕获呢?

domain的深层次保护

既然大家看到了上面的问题,那么最佳的方案就是使用domain来解决这种异步异常捕获问题。那么如何应用domain来处理呢?请看下面示例代码:

var domain = require('domain');
var d = domain.create();
d.on('error',function(err){
    console.log(err);
});
function callErr(){
    setTimeout(function(){
        var s = wrong + true;
        console.log(someSth);
    }, 10);
}
d.run(function(){
    callErr();
})

非常的简单,就是将需要保护的代码逻辑包裹在d.run的回调函数中即可,这样可以保证整个服务运行期间这部分调用逻辑安全,我最新的myweb2.1就是使用domain来保护整个逻辑的安全。

整体上这部分健壮性的就介绍完了,本次介绍的重点是如何保证服务器的代码逻辑异常,避免代码异常导致的服务器进程退出,关于服务器运行时安全的问题,我们将在下一个视频课时出来以后我这边再做相应的文章编写。