node.js只强制一个线程执行代码

时间:2017-11-03 14:03:16

标签: javascript node.js multithreading socket.io

当我启动我的应用程序node app.js时,运行的进程只有一个线程。无论运行的时间越长,为该进程制作的线程越多。问题是,当我想执行这样的特定代码时:

var io = require('socket.io')(process.env.PORT);

失败是因为信号是从多个线程发送的,因此代码未成功执行。

简单测试,如果有人这样做:

var io = require('socket.io')(9001);
var io = require('socket.io')(9002);
var io = require('socket.io')(9003);
var io = require('socket.io')(9004);

它可以正常工作,但是这段代码:

var cPort = 9001;
setInterval(function() {
    var io = require('socket.io')(cPort);
    cPort++;
}, 1000 * 60 * 2); // 1 sec * 60 seconds * 2 = 2 minutes interval

不会被执行,因为2分钟后节点会有很多线程并且他们都会尝试执行代码 - 因此你会看到error: address in use

所以尽管运行同一个文件的多线程进程,我怎么能强制节点只执行一次这个代码?

06.11.2017编辑----

澄清问题:

我的意思是问题,我没有资源问题,如果我一次启动所有服务器(例如40台服务器),它们都已成功启动并无限期地工作。如果我只启动一个服务器,然后运行在需要时自动启动更多的代码,则会出现问题。此时我总是看到address in use错误,显然地址在代码执行时没有被使用。目前我必须在周末手动启动更多服务器,因为有更多人在一周中的其他几天使用服务和更少服务器,我想创建基于人口启动和关闭服务器的自动化系统。

这是服务器启动代码:

var cp = require('child_process'),
    servers = [],
    per_server = config.per_server,
    check_servers = function(callback) {
        for(var i = 0; i < servers.length; i++) {
            callback(i, servers[i]);
        }
    };

this.add_server = function(port) {
    var server = {
        port: port,
        load: 0,
        process: cp.fork(__dirname + '/../server_instance.js', [], {
            env: {
                port: port
            }
        })
    };

    server.process.on('message', function(message) {
        server.load = message.load;
    });

    servers.push(server);
};

this.find_server = function() {
    var min = Infinity,
        port = false;

    check_servers(function(index, details) {
        if(details.load < min) {
            min = details.load;
            port = details.port;
        }
    });

    return port;
};

现在,如果我连续执行controller.add_server() 40次,它将正确启动40台服务器,但如果我这样做:

var start_port = 3185;
setInterval(function() {
    var min = Infinity;

    check_servers(function(index, details) {
        if(details.load < min) {
            min = details.load;
        }
    });

    if(min > config.per_server) {
        controller.add_server(start_port);
        start_port++;
    }
}, 5000);

我在第二次,第三次或第四次创建服务器时发现该地址已被使用的随机错误。

07.11.2017编辑----

根据建议,我尝试使用以下库进行端口扫描/查找:

只使用第一个,我能够启动至少2台服务器,这是我使用的代码:

setInterval(function() {
    var min = Infinity;

    check_servers(function(index, details) {
        if(details.load < min) {
            min = details.load;
        }
    });

    if(min > per_server) {
        _self.add_server();
    }
}, 5000);

var portfinder = require('portfinder');
portfinder.basePort = 3185;

this.add_server = function() {
    portfinder.getPortPromise()
        .then((port) => {
            console.log('port found', port);

            var server = {
                port: port,
                load: 0,
                process: cp.fork(__dirname + '/../server_instance.js', [], {
                    env: {
                        port: port
                    }
                })
            };

            server.process.on('message', function(message) {
                server.load = message.load;
            });

            servers.push(server);

        })
        .catch((err) => {
            console.log('error happened');
        });
};

经过多次测试后,看起来我可以启动2台服务器然后随机,在第三次或第四次尝试时崩溃。很明显,问题更深入,然后端口查找,这个库只告诉我我已经知道的东西,我知道打开了什么端口,并且我在脚本尝试使用手动netstat -anp | grep PORT命令尝试启动服务器之前仔细检查。

所以很清楚,问题不在于找到打开的端口,从结果的角度看,节点试图从单个命令多次启动服务器。

跟进编辑----

添加server_instance.js代码:

var io = require('socket.io')(process.env.port),
    connections_current = 0,
    connections_made = 0,
    connections_dropped = 0;

io.on('connection', function(socket) {

    connections_current++;
    connections_made++;

    // ... service logic here, not relevant (like query db, send data to users etc)

    socket.on('disconnect', function() {
        connections_current--;
        connections_dropped++;
    });

});

setInterval(function() {
    process.send({
        load: connections_current
    });
}, 5000);

08.11.2017编辑----

我正在测试许多解决问题的解决方案,我观察到了这种情况:

  • 在mac osx上进行本地测试,我可以在服务器上生成最多3000个连接。错误永远不会发生,节点有路由器文件1 process6 threads。通过3000个连接,我甚至可以生成200台服务器而没有任何问题。

  • linux debian上的服务器测试,我在服务器上生成2毫升连接。错误总是发生在第3或第4个服务器实例上,当我连接所有人节点时,路由器文件有6 processes10 threads for every process

这显然是问题的根源,我拥有的容量越多,节点产生的进程就越多,并且在尝试启动新服务器时它会越快重叠。

2 个答案:

答案 0 :(得分:1)

最好的解决方案是在主进程中生成端口号,然后将它们传递给工作进程,以便那些不相交。

此外,您可以检查端口是否正在使用,并使用test-port-provider等npm模块获取空闲端口。

答案 1 :(得分:0)

您可以使用portfinder包来发现系统中的可用网络端口(它从端口8000开始发现)。用法很简单:

const http = require('http');
const portfinder = require('portfinder');
const pid = process.pid;


portfinder.getPort((err, port) => {
    if (err)
        throw err;

    http.createServer((req, res) => {         
        res.end(`Response from server ${pid}.\n`);
    }).listen(port, () => {
        console.log(`Server ${pid} running on port ${port}...`);
    });    
});



** 编辑 **

似乎从portfinder多次返回相同的端口,因此抛出了EADDRINUSE错误。我怀疑是当portfinder试图找到一个新端口(因此返回相同的端口)时端口还没有监听,但这似乎与以下事实相矛盾:使用简单的for循环启动多个服务器似乎工作正常:< / p>

for (let i = 0; i < max_number_of_servers; ++i) {
    this.add_server();
}


对代码的一个简单修复可能是在每次调用add_server时增加portfinder的基址:

portfinder.basePort = 8000;

this.add_server = function() {
        portfinder.getPortPromise()
        .then((port) => {   

            portfinder.basePort += 1;

            var server = {
                port: port,
                load: 0,
                process: cp.fork('server_instance.js', [], {
                    env: {
                        port: port
                    }
                })
            };

            server.process.on('message', function(message) {
                server.load = message.load;
                console.log("message");
            });

            servers.push(server);

        })
        .catch((err) => {
            console.log(err);  
        });   
};

此代码似乎工作正常,至少在我的机器上。
无论如何,我建议你考虑一个不同的实现。 Imho如果您发现在最高流量情况下您需要N台服务器来正确处理所有请求,则无需创建较少数量的服务器,然后根据当前流量动态更改它,原因如下:

  • 新流程是一项昂贵的操作,可能需要一段时间才能启动并运行。
  • 如果流量很大,您的所有服务器都已准备就绪,无需额外延迟即可提供服务。
  • 如果流量较低/中等,您的服务器将会减少过载,但您会获得弹性和可用性(如果服务器进程崩溃,无论出于何种原因,还有许多其他服务器可能会在您处理请求时)可以启动一个需要一些时间的新服务器进程。


您可以使用本机cluster模块轻松构建具有自动负载平衡和容错功能的进程分布式服务器应用程序。默认情况下,clusteer模块执行循环算法以在工作者之间分配传入请求,因此您可以免费获得负载平衡!
一个可能的简单实现(仅用于测试我使用了不同的port finder package):

// main.js

const cluster = require('cluster');
const getPort = require('get-port');
const max_servers = 40;

// master process
if (cluster.isMaster) {
    for (let i = 0; i < max_servers; ++i) {
        getPort().then(port => {
            cluster.fork({port: port});
        })          
    }
    // detect exit event on workers
    cluster.on("exit", (worker, errCode) => {
        console.log(worker);
        // start new worker in case of crashes
        if (errCode != 0 && !worker.suicide) {
            console.log("Worker-server crashed. Starting new worker...");
            getPort().then(port => {
                cluster.fork({port: port});
            })
        }
    });
}
// worker process --> start server
else {
    require('./server_instance.js'); // [2]
}
// server_instance.js

const http = require("http");
const pid = process.pid;
let port = process.env.port;

console.log(`Starting server on process ${pid} running on port ${port}...`);

let io = require('socket.io')(process.env.port),
    connections_current = 0,
    connections_made = 0,
    connections_dropped = 0;

io.on('connection', function(socket) {
    console.log(`Socket.io on process ${pid} running on port ${port}...`);
    connections_current++;
    connections_made++;

    // ... service logic here, not relevant (like query db, send data to users etc)

    socket.on('disconnect', function() {
        connections_current--;
        connections_dropped++;
    });

});