Node.js: Limit forks in cluster - node.js

I have a Web application and an application that runs in background, this last load much the server, so far has been working properly, but in the last version to add new features, carries a lot of load and blocks the server.
I have been trying to modify the code that runs the cluster, but always load the same number of processes.
This is my code:
var cluster = require('cluster'),
limitWorkers = Math.ceil(require('os').cpus().length / 2),
jobWorkers = [],
webWorkers = [];
if(cluster.isMaster) {
//let limit = 0;
require('os').cpus().forEach(function() {
addWebWorker();
/*if(limit < limitWorkers) {
limit++;
addJobWorker();
}*/
if( limitWorkers > 0 ) {
--limitWorkers;
addJobWorker();
}
});
cluster.on('exit', function(worker, code, signal) {
if (jobWorkers.indexOf(worker.id) != -1) {
console.log('job worker ' + worker.process.pid + ' died. Trying to respawn...');
removeJobWorker(worker.id);
addJobWorker();
}
if (webWorkers.indexOf(worker.id) != -1) {
console.log('http worker ' + worker.process.pid + ' died. Trying to respawn...');
removeWebWorker(worker.id);
addWebWorker();
}
});
} else {
console.log('start http server: ' + cluster.worker.id);
require('./apps/web-http'); //initialize the http server here
//if( process.env.job === 1 ){
if( jobWorkers.indexOf(cluster.worker.id) != 1 ) {
//if( limitWorkers > 0 ) {
//limitWorkers--;
console.log('start job server: ' + cluster.worker.id);
require('./apps/jobs-worker'); //initialize the job here
//}
}
}
function addWebWorker() {
webWorkers.push(cluster.fork({web: 1}).id);
}
function addJobWorker() {
jobWorkers.push(cluster.fork({job: 1}).id);
}
function removeWebWorker(id) {
webWorkers.splice(webWorkers.indexOf(id), 1);
}
function removeJobWorker(id) {
jobWorkers.splice(jobWorkers.indexOf(id), 1);
}
Any idea to run the job worker half the cores of the processor or just one? Thanks.

Related

Cron Job running multiple times under cluster environment in Node js

i have set up Node JS server cluster environment to fully utilize all the cores of my server. There is a Cron Job which runs every day 08 O'clock to run some tasks. But due to clustering it runs 4 times(server is of 4 cores) every day at 08 O'clock.
How can i over come this problem to run Cron Job only once a day?
if(cluster.isMaster) {
var numWorkers = require('os').cpus().length;
console.log('Master cluster setting up ' + numWorkers + ' workers...');
for(var i = 0; i < numWorkers; i++) {
cluster.fork();
}
cluster.on('exit', function(worker, code, signal) {
console.log('Worker ' + worker.process.pid + ' died with code: ' + code + ', and signal: ' + signal);
cluster.fork();
});
} else {
var CronJob = require('cron').CronJob;
new CronJob('01 30 08 * * 0-6', function() {
console.log('Running Schedular');
//Performing tasks
}, null, true, 'America/Los_Angeles');
var server = app.listen(port, function() {
console.log('Process ' + process.pid + ' is listening to all incoming requests');
});
}
Hi Please find the below changes, I just changed cron job into if condition.
var CronJob = require('cron').CronJob;
if(cluster.isMaster) {
var numWorkers = require('os').cpus().length;
console.log('Master cluster setting up ' + numWorkers + ' workers...');
for(var i = 0; i < numWorkers; i++) {
cluster.fork();
}
cluster.on('exit', function(worker, code, signal) {
console.log('Worker ' + worker.process.pid + ' died with code: ' + code + ', and signal: ' + signal);
cluster.fork();
});
new CronJob('01 30 08 * * 0-6', function() {
console.log('Running Schedular');
//Performing tasks
}, null, true, 'America/Los_Angeles');
} else {
var server = app.listen(port, function() {
console.log('Process ' + process.pid + ' is listening to all incoming requests');
});
}
Let the master cluster handle the cron job and not one of the worker threads.

ZeroMQ: How to notify a Publisher from a Subscriber

I use PUB/SUB ZeroMQ pattern.
System consists from Web Server ( Publisher ), clustered TCP servers ( Subscribers ) and external applications ( clients, which connect to TCP servers ).
Huge amount of external clients connect to every TCP server. Every external client has unique peerId which I use as topic in Publisher. For some management purposes I send messages to TCP servers ( like remove peer, change, etc. ). But also I need to send messages from TCP server to Web Server ( connect, disconnect, error ). I didn't find right way how to do it. Can anybody suggest how to do it correctly?
Update 1
It looks like using ROUTER/DEALER pattern is the most convenient for that.
Some comments about scripts.
External clients connect to tcp servers ( cluster ) and send unique peerId, on tcp server side tcp socket cached by unique peerId. Then tcp server sends peerId message by ZeroMQ socket to Web Server. Web Server caches envelope by peerId. Every n milliseconds Web Server sends messages to random peer ( generate 'peerId' ). TCP Server receives these messages, gets correct tcp socket from cache and sends theirs to clients. Clients calculate count of messages and every n milliseconds send their to TCP server, which sends count to WEB Server by ZeroMQ socket. On Web Server every n milliseconds count of sended and received messages are printed on console.
Test js script of server part:
var cluster = require('cluster'),
zmq = require('zmq'),
net = require('net'),
zmqport = 'tcp://127.0.0.1:12345';
var count = 10;
var countPeers = 10000;
var interval = 1;
if (cluster.isMaster) {
for (var i = 0; i < count; i++) cluster.fork({
TCP_SERVER: 1
});
cluster.fork({
WEB_SERVER: 1
});
cluster.on('death', function (worker) {
console.log('worker ' + worker.pid + ' died');
});
} else {
if (process.env.TCP_SERVER) {
var sockets = Object.create(null);
var socket = zmq.socket('dealer');
socket.identity = 'process-' + process.pid;
socket.connect(zmqport);
socket.on('message', function (peerIdBuffer) {
var peerId = peerIdBuffer.toString();
if (typeof sockets[peerId] !== 'undefined') {
var buffer = new Buffer(4);
buffer.writeUInt32BE(1, 0);
sockets[peerId].write(buffer);
}
});
var server = net.createServer(function (tcpsocket) {
tcpsocket.on('data', function (data) {
if (!tcpsocket.peerId) {
var peerId = data.toString();
sockets[peerId] = tcpsocket;
tcpsocket.peerId = peerId;
return socket.send(['id', data]);
}
return socket.send(['count', data]);
});
});
server.listen('13333', '0.0.0.0');
} else {
var countMessagesSended = 0;
var countMessagesReceived = 0;
var socket = zmq.socket('router');
var clients = Object.create(null);
socket.bind(zmqport, function (err) {
if (err) throw err;
setInterval(function () {
for (var i = 0; i < countPeers; i++) {
var topic = Math.floor(Math.random() * countPeers) + '-peer';
if (typeof clients[topic] !== 'undefined') {
countMessagesSended++;
socket.send([clients[topic], topic]);
}
}
}, interval);
});
socket.on('message', function (envelope, messageId, data) {
switch (messageId.toString()) {
case "id":
clients[data.toString()] = envelope.toString();
break;
case "count":
countMessagesReceived += data.readUInt32BE(0);
break;
}
});
setInterval(function () {
console.log('%s messages have been sended, %s - received', countMessagesSended, countMessagesReceived);
countMessagesSended = 0;
countMessagesReceived = 0;
}, 5000);
}
}
Test js script for clients:
var cluster = require('cluster'),
net = require('net');
var count = 10;
if (cluster.isMaster) {
for (var i = 0; i < count; i++) cluster.fork({
CLUSTER: i
});
cluster.on('death', function (worker) {
console.log('worker ' + worker.pid + ' died');
});
} else {
var clientspernode = 1000;
var offset = parseInt(process.env.CLUSTER, 10);
for (var j = (offset) * clientspernode; j < (offset + 1) * clientspernode; j++) {
(function (j) {
var countMessages = 0;
var client = net.connect({
port: 13333,
host: '127.0.0.1'
}, function () {
client.write(j + '-peer');
});
client.on('data', function (buffer) {
countMessages += Math.ceil(buffer.length / 8);
});
client.on('error', function () {
});
setInterval(function () {
var buf = new Buffer(4);
buf.writeUInt32BE(countMessages, 0);
client.write(buf);
countMessages = 0;
}, 5000);
})(j);
}
}

How to make a node.js application keep listening after closing a socket?

I'm trying to set up a node.js application that could receive connections and still be listening to port 9001 once a socket is ended. How can I do that? Here is my current code (it doesn't close after the socket.end(), but it won't accept any other connections) :
var net = require('net');
var mySocket;
var server = net.createServer(function(socket) {
mySocket = socket;
mySocket.on("connect", onConnect);
mySocket.on("data", onData);
});
function onConnect() {
console.log("Connected");
}
function onData(command) {
if (command == "exit") {
console.log("Exiting");
mySocket.end();
}
}
console.log("Waiting for incoming connections");
server.listen(9001);
I tried to add another server.listen(9001); after the socket.end();, but I get a : Error: listen EADDRINUSE message.
Also, will that code be able to receive several connections coming from different addresses at the same time, and handle them separately?
This is the full code. When executed, node.js receives 4 commands from the Flash application, and works properly (except that the onConnect() function seems never to be called), and the "exit;" command closes the socket properly, yet if I reload the Flash application, it doesn't connect to the server
var net = require('net');
const PACKET_SEPARATOR = 59 // ;
var connection_ack = false;
var counter = 0;
var server = net.createServer(function(socket) {
function onConnect() {
console.log("Connected to Flash");
}
function dataHandler(command) {
if (command[command.length - 1] != String.fromCharCode(PACKET_SEPARATOR) && connection_ack) {
console.log("SEP : " + PACKET_SEPARATOR + " - last : " + command[command.length - 1] + " - ack " + connection_ack);
console.log("CAUGHT EXCEPTION : WRONG PACKET FORMAT --- " + command + " --- " + command.length);
}
if (command == "exit;") {
console.log("Received exit request from " + socket.address().address + ":" + socket.address().port + " (" + socket.address().family + "). Ending connection...");
socket.end();
}
else if (command == "<policy-file-request/>\0") {
socket.write('<cross-domain-policy>\n<allow-access-from domain="*" to-ports="*" />\n</cross-domain-policy>\0', 'utf8');
console.log("Policy file sent to " + socket.address().address + ":" + socket.address().port);
player1.pxacceleration = 0;
player1.pyacceleration = 0;
connection_ack = true;
}
else {
console.log("Got data from " + socket.address().address + ":" + socket.address().port + " (" + socket.address().family + ")");
console.log("--> " + command);
counter++;
socket.write("Received " + counter + " commands;", 'utf8');
console.log("Sending : Received " + counter + " commands;");
}
}
function onData(d) {
var command = "";
for (i=0; i <= d.length - 1; i++) {
command += String.fromCharCode(d[i]);
if (d[i] == PACKET_SEPARATOR || i == d.length - 1 && !connection_ack) {
dataHandler(command);
command = "";
}
}
}
socket.on("connect", onConnect);
socket.on("data", onData);
});
console.log("Ready. Waiting for incoming connections");
server.listen(9001);
server.listen(80); //TODO : Remove?
As jfriend00 said, using mySocket as a global is not recommended. Try the below instead.
var server = net.createServer(function(socket) {
function onData(command) {
if (command == "exit") {
console.log("Exiting");
socket.end();
}
}
socket.on("connect", onConnect);
socket.on("data", onData);
});
...
This eliminates the need for the global in the first place. This should also allow multiple sockets and prevent the original error. I think. I'm new here, so I guess we will see.
EDIT
Alright. I've been interacting with your code via telnet. I've also read up on some of the documentation. First, the socket.on("connect", onConnect); listener should be moved(along with the onConnect function) into the global scope and changed to server.on("connection", onConnect);. The reason for this is that the socket event listener connect is a client side listener. We are working server side. The server side listener for new connections is connection and the server should be listening for it in the same way it is listening for connections on a particular port.
This part of your code should look like this now:
//more code up above here
....
function onData(d) {
var command = "";
for (i=0; i <= d.length - 1; i++) {
command += String.fromCharCode(d[i]);
if (d[i] == PACKET_SEPARATOR || i == d.length - 1 && !connection_ack) {
dataHandler(command);
command = "";
}
}
}
socket.on("data", onData);
});
function onConnect() {
console.log("Connected to Flash");
}
server.on("connection", onConnect);
....
//more code below here
However, the code would not recognize exit as a command via telnet. I was unable to figure this bit out. Since your question did not cover this problem it might just be me or you have it figured out.
EDIT 2
The code below keeps it local.
var server = net.createServer(function(socket) {
function onConnect() {
console.log("Connected to Flash");
socket.write("we're connected");
}
....
function onData(d) {
var command = "";
for (i=0; i <= d.length - 1; i++) {
command += String.fromCharCode(d[i]);
if (d[i] == PACKET_SEPARATOR || i == d.length - 1 && !connection_ack) {
dataHandler(command);
command = "";
}
}
}
onConnect();
socket.on("data", onData);
});

Node Async Looping - Why does the memory grow and just drop all of the sudden?

I'm writing a program in Node that uses an async loop. The goal is to get this program to run on Heroku for an extended period of time. It grows in memory, as expected. But then once the memory usage hits about 57MiB, it drops back down to 22MiB (where it started). What causes the memory usage to drop out of nowhere like that?
Here is my code, if it helps at all. database.read is just a simplification of http.request.
var http = require("http");
var util = require('util');
var fnstraj = require("./predictors/fnstraj.js");
var database = require("./library/database.js");
var COUNT = 0;
////////////////
// Queue Loop //
////////////////
var worker = function() {
setTimeout(function() {
COUNT++;
console.log("Worker Clock: " + COUNT + ".");
console.log(util.inspect(process.memoryUsage()));
database.read('/queue/', function( results, error ) {
if ( typeof error !== "undefined" && error ) {
process.nextTick( worker );
} else {
var queue = results;
database.read('/flights/', function ( results, error ) {
if ( typeof error !== "undefined" && error ) {
process.nextTick( worker );
} else {
var flights = results;
if ( !flights.error && typeof queue.rows[0] !== "undefined" ) {
for ( flight in flights.rows ) {
if ( flights.rows[flight].doc._id === queue.rows[0].doc._id ) {
var thisFlight = flights.rows[flight].doc;
console.log("Flight " + thisFlight._id + " started");
thisFlight.duration = fnstraj.vertPred(thisFlight.launch.altitude, thisFlight.balloon.burst, thisFlight.balloon.radius, thisFlight.balloon.lift);
fnstraj.predict(thisFlight, function() {
database.remove('/queue/' + thisFlight._id);
console.log("Flight " + thisFlight._id + " completed");
process.nextTick( worker );
});
var found = true;
}
}
if ( !found ) {
process.nextTick( worker );
}
}
}
});
}
});
}, 25);
};
This is related to the V8 Garbage collection. You may tweak it with the '--gc_interval ' option of node but note that this is an advanced parameter.
node --gc_interval <allocation count interval>
This may also be related to the heap compression of the GC. Tis is the process of gathering all the previously freed space and eventually give is back to the Operating System.
for more tweaks, you can experiments with V8 specific options:
node --v8-options

Handling byte streams in node.js

For education purposes I am creating a little chat with node.js using TCP.
I am using the windows console to connect with my node server but when I am typing all the characters are streamed one by one. They don't arive as strings. How can I manage to handle those streams so my users don't can write complete words.
My Code:
var net = require("net");
Array.prototype.remove = function(e) {
for (var i = 0; i < this.length; i++) {
if (e == this[i]) { return this.splice(i, 1); }
}
};
function Chatter(stream) {
this.name = null;
this.stream = stream;
}
var chatters = [];
var server = net.createServer(function(stream) {
var chatter = new Chatter(stream);
chatters.push(chatter);
stream.setTimeout(0);
stream.setEncoding("utf8");
stream.addListener("connect", function(){
stream.write("Hallo, wer bist du?:\n");
});
stream.addListener("data", function (data) {
if(chatter.name == null) {
chatter.name = data.match(/\S+/);
stream.write("....................\n");
chatters.forEach(function(c){
if (c != chatter) {
c.stream.write(chatter.name + " ist dem Chat beigetreten!\n");
}
});
return;
}
var command = data.match(/^\/(.*)/);
if (command) {
if (command[1] == 'users') {
chatters.forEach(function(c) {
stream.write("- " + c.name + "\n");
});
}
else if (command[1] == 'quit') {
stream.end();
}
}
chatters.forEach(function(c) {
if(c != chatter) {
c.stream.write(chatter.name + ": " + data);
}
});
});
stream.addListener("end", function(){
chatters.remove(chatter);
chatters.forEach(function(c) {
c.stream.write(chatter.name + " hat den Chat verlassen.\n");
});
stream.end();
});
});
server.listen(8000);
For the record that code is from this site
ADDITION:
setEncoding('utf8') is supposed to change the emiting of data, but it doesn't work for me :-(
The solution to your problem is to store all received characters in a buffer and when an END_OF_NICK character is encountered (say, \n), use the buffer as the name.
var buffer = ""; // stores received characters
stream.addListener("data", function (data) {
if(chatter.name == null) { // still receiving characters for the name
buffer += data; // append received characters to the buffer
if (buffer.indexOf('\n') == -1) return; // if there's no END_OF_NICK character, keep waiting for it
chatter.name = buffer.match(/\S+/); // use the name in the buffer
// ...
}

Resources