1

引子:

最近在调试一个nodejs的工程,这个工程是由国外team维护的,我们可能需要了解其内部的一些关于token运行机制。由于暂时没有权限获得源码,只有一个已经部署好服务的测试环境供我做research,所以我唯一想到的就是用node-inspector这个debugger工具来远程调试一下代码以方便调研。

但是整个过程却没有想象中顺利,当中躺了一些坑。本文就是介绍我遭遇的经历以及我的解决这些问题的思路,让大家以后能顺利的debug较为复杂nodejs程序。

问题1 node-inspector似乎没有work,chrome获取不到代码
按照以往经验,只需要在启动server.js的地方加上 --debug参数,然后运行node-inspector 便能,在chrome中输入 http://service_domain:8080/?port=5858进行断点调试了。

node --debug start.js

但是这么做chrome 访问http://service_domain:8080/?port=5858 木有显示出源码,当中必有蹊跷。
于是我查看了 start.js, 原来奥秘就在其中,start.js并不是一个具体的服务,经过层层剖析,它实际上最终运行了这样一份代码

const forever = require('forever');
const path = require('path');
const cwd = process.cwd();

module.exports = {
    start:function(){
        var logFile = path.resolve(cwd,'log/forever_log.log');
        var script = path.resolve(cwd,'kluster.js');
        forever.startDaemon(script,{
            silent: false,
            uid:'demo',
            append: false,
            max: 1,
            logFile: logFile,
        });
    }
};

观察了一下似乎找到原因了,实际上真正的script是由forever这个工具来启动的,我们可以看到forever.startDaemon(script,opts}中的script才是真正的服务脚本,于是查阅了foreverforever-monitor的文档,发现需要在opts中加--debug的启动参数。于是我就添加了command:"node --debug" 这一句来启动真正的服务。

forever.startDaemon(script,{
            silent: false,
            uid:'demo',
            append: false,
            max: 1,
            logFile: logFile,
            command: 'node --debug'
        });

好,这个时候再次运行node-inspector后,令人激动的场景出现了,服务端的code显示在chrome中,正当我以为一切都搞定的时候,下一个坑又出现了。

问题2 node-inspector已经成功显示了源码,但是加了breakpoint后却hit不了
这个问题让我百思不得其姐,我甚至都放弃了node-inspector,而改用node原生的在代码中添加debugger语句的方式来调试代码都不成功,断点始终是失效的。于是只好再深入剖析代码了。

var script = path.resolve(cwd,'kluster.js');

之前使用forever启动的Kluster.js这份文件必有蹊跷。

const cluster = require('cluster');
const numCPUs = require('os').cpus().length;
const http = require('http');

if (cluster.isMaster) {
    console.log("master start...");
    // Fork workers.
    for (var i = 0; i < numCPUs; i++) {
        cluster.fork();
    }
    cluster.on('listening', function(worker, address) {
        console.log('listening: worker ' + worker.process.pid + ', Address: ' + address.address + ":" + address.port);
    });
    cluster.on('exit', function(worker, code, signal) {
        console.log('worker ' + worker.process.pid + ' died');
    });
} else {
    http.createServer(function(req, res) {
        res.writeHead(200);
        res.end("hello world\n");
    }).listen(3000);
}

原来项目中使用了cluster这个module来进行了负载均衡,也就是说nodejs会起n个子进程来同时监听3000端口,我们也可以查看下node-inspector的log输出

Debugger listening on port 5858
master start...
Debugger listening on port 5859
Debugger listening on port 5860
Debugger listening on port 5861
Debugger listening on port 5862
listening: worker 9968, Address: null:3000
listening: worker 12588, Address: null:3000
listening: worker 9128, Address: null:3000
listening: worker 10856, Address: null:3000

终于真相大白了,原来5858监听的是master进程,进程上并没有跑实质的service所以导致断点截获不了
只要将http://service_domain:8080/?port=5858中的port改成5859~5862中的任意一个端口就可以监听子进程了。果然断点似乎是可以断住了。

http://service_domain:8080/?port=5860

问题3 断点有时生效有时不生效
改变port端口为5060后发现断点可以断住了,但是又有一个新问题产生了,那就是有时候断点仍然会失效,这个问题就很好分析了,因为负载均衡时服务器将请求分配给某个子进程是不固定的,也就是说我页面上发出的request有可能分配给5859端口上的worker1来处理,也有可能被分配个其它端口上的worker来处理。所以光监听5860端口还是不够的。难道我们需要开4个chrome监听所有的端口嘛?这样太过麻烦,那么我们不妨就来做减法,减少子进程的数量,把它控制在1条子进程,这样我们就只需要监听5859一个端口就可以截获所有的request的请求了。

const cluster = require('cluster');
//const numCPUs = require('os').cpus().length;
const numCPUs = 1;

至此,我们就可以使用node-inspector愉快地调试我们的业务代码了。
这里给出本文示例的code: https://github.com/momoko8443/nodejs_demo_area

参考文献:
解读Nodejs多核处理模块cluster
node.js debugging with node-inspector and forever.js
What’s New in Node.js v0.12: Debugging Clustered Apps with Node-Inspector


熊丸子
5.6k 声望293 粉丝

现在sf的文章质量堪忧~~~