nodejs的domain到底肿么了??
发布于 11 年前 作者 networkwx 9742 次浏览 最后一次编辑是 8 年前

最近在项目里发现一个奇怪的问题,我们使用nodejs去访问mysql或者redis的时候,如果回调函数出错了,domain居然抓不到,很纳闷,于是我模拟了一个这样的场景来说明这个问题。我先说一下运行过程,下面会贴出来两段代码,先运行server.js(这是一个类似redis的服务),然后运行server2.js(这个是web server服务),然后通过浏览器访问localhost:8080,第一次能看到被domain抓住了,然后打印了出错信息,但是当你再次刷新浏览器的时候,你会发现抛出的错误没被domain抓到,于是进程就退出了。。大家一起研究研究,这到是什么问题。。

server.js

var net = require('net');

net.createServer(function(conn){
console.log("new client");

conn.on("data", function(chunk){
	console.log("data:", chunk.toString());
	conn.write("hi " + chunk);//这里模拟数据处理完毕并且返回数据
});

conn.on("close", function(){
	console.log("client end");
});
}).listen(1104);

server2.js

var net = require('net'),
    http = require('http'),
    domain = require('domain');

var conn = null;	
function getConnection(cb)
{
if(conn){
	cb(conn);
}else{
	var client = new net.Socket();
	
	client.connect(1104);
	
	client.on("connect", function(){
		conn = client;
		cb(conn);
	});
	
	client.on("data", function(chunk){
		console.log("recv data");
		throw new Error("process error");//这里模拟回调出错
	});
	
	client.on("end", function(){
		conn = null;
		consoel.log("conn end");
	});
}
}

http.createServer(function(req, res){
var reqd = domain.create();

reqd.on("error", function(err){
	console.log("Domain Error:", err.stack);
	reqd.dispose();
});

reqd.run(function(){
	getConnection(function(conn){
		conn.write("test", function(){
			console.log("write end");
		});
	});
});
}).listen(8080);
11 回复

难道没有人遇到这样的问题??不可能吧!!!

reqd.dispose();

此行注释掉后能正常 catch error 这可能是个bug。 reqd与geConnection绑定而后dispose了一次,估计dispose阻止后续domain再与getConnect进行绑定,因此第二次http请求抛出的error就没有被catch

这样是不行的,你试着把reqd.dispose()改成res.end(err.stack),表示把服务器的错误信息返回给浏览器,你会发现,第一次刷新页面没有问题,但第二次、第三次之后所有请求都会一直处于加载状态。

//getConnection(function(c){ console.log('got connection'); });
var req_counter=0;
http.createServer(function(req, res){
    req_counter++;
    console.log('>>>>>>request: %s<<<<<<<',req_counter);
    var reqd = domain.create();
    res.counter=req_counter;
    reqd.counter=req_counter;
    console.log('[request]res.counter=%s',res.counter);
    console.log('[request]reqd.counter=%s',reqd.counter);
    reqd.on("error", function(err){
        console.log('[error]res.counter=%s',res.counter);
        console.log('[error]reqd.counter=%s',reqd.counter);
        res.end(err.stack);
        //reqd.dispose();
        //if(conn){ conn.end(); conn=null; }
    });

    reqd.run(function(){
        getConnection(function(conn){
            conn.write("test", function(){
                console.log("write end");
            });
        });
    });
}).listen(8080);

输出信息

>>>>>>request: 1<<<<<<<
[request]res.counter=1
[request]reqd.counter=1
recv data
[error]res.counter=1
[error]reqd.counter=1
write end
>>>>>>request: 2<<<<<<<
[request]res.counter=2
[request]reqd.counter=2
recv data
[error]res.counter=1
[error]reqd.counter=1
write end
>>>>>>request: 3<<<<<<<
[request]res.counter=3
[request]reqd.counter=3
write end
recv data
[error]res.counter=1
[error]reqd.counter=1

这解释了为什么取消reqd.dispost()后,第2次、3次一直加载,因为on(‘error’)回调函数中的res对象指向的一直是第一次请求所对应的response,同样,捕捉到异常的domain对象也一直第一次请求时生成的domain。第2、3次请求所生成的domain对象完全没起到作用。

取消下面2行的注释

    //reqd.dispose();
    //if(conn){ conn.end(); conn=null; }

再测试一次,输出信息

>>>>>>request: 1<<<<<<<
[request]res.counter=1
[request]reqd.counter=1
recv data
[error]res.counter=1
[error]reqd.counter=1
conn end
>>>>>>request: 2<<<<<<<
[request]res.counter=2
[request]reqd.counter=2
recv data
[error]res.counter=2
[error]reqd.counter=2

这应该才是楼主原程序预期的效果。假如取消下面这行的注释

//getConnection(function(c){ console.log('got connection'); });

则后续所有的domain均失效。

domain.run(function()…此隐式(Implicit)绑定是直接绑定底层的io对象,同一个io对象只能进行一次隐式绑定,若要绑定第二个domain则要显式(Explicit)绑定domain.add(…),也就是第2次、3次要reqd.add(conn)。但由于第一次请求已经dispose过,底层conn已经关闭连接,后续的conn.write将失效,conn不会再接受到任何的数据,则又卡死运行流程。

总之,目前看来domain是个很艹蛋的东西,而dispose则又是非常糟糕的api,关于这个接口的open issue就有3个… https://github.com/joyent/node/pull/3559 https://github.com/joyent/node/issues/5019 https://github.com/joyent/node/pull/4153

1、我这里只是做个demo,在实际的开发中,httpServer层跟应用层是分开的,而getConnection这个东西是在应用层,不可能让一外层的服务去涉及应用的层的东西吧? 2、就算在httpServer去调用应用层的closeConn方法,但因为这是一个共享的conn对像,而在这个domain发生错误之前,已经有其它请求已经拿到这个共享的conn正准备用,这时候这个domain因为出错,去关闭了conn对像,其它请求里面用到这个conn对像就全出问题了; 3、如果不使用共享conn对像,那么一个请求就是一个连接,对于nodejs没有一个固定的并发请求数量控制,如果这个conn是一个mysql,估计mysql早就开始报连接已满了; 4、本来使用共享的conn就为了实现连接池,就因为这样连接池功能也成了泡影了。

关于domain, 之前写过一篇blog,想通过domain来解决异步的error问题,还是很容易踩坑的吖

还是没有一个好方案来解决这个问题。

@dead-horse 原来那篇东西是你写的呀,真是令人赞叹,高手啊

@dead-horse 真是受教了,我今天还去看了domain的API。

是因为conn在外面定义了 有api可以讲conn加进去 From Noder

又被挖坟了… domain 自身有很多局限性,而且导致 node core 也很难维护,所以已经被 depracated 了。所以这个问题也不用再被挖出来了 =。=

详细的讨论:https://github.com/nodejs/node/issues/66

回到顶部