According to node.js documentation, the cluster class should have a workers object and it can be iterated like this:
Object.keys(cluster.workers).forEach((id) => {
cluster.workers[id].on('message', messageHandler);
});
but for some reason when I try this in my code, cluster.workers is undefined (process launched with pm2)
var cluster = require("cluster");
console.log("cluster status : " , cluster.isMaster ? " master " : " slave " , " - worker id " , (cluster.worker ? cluster.worker.id : " none ") , " workers : " , cluster.workers);
I got output like this:
cluster status : slave - worker id 15 workers : undefined
cluster status : slave - worker id 14 workers : undefined
so I guess workers are only available from master , but is there a way to communicate with other slaves for a slave ?
You're right, the 'workers' property is only available in the master process (see latest documentation).
I don't think there's any way for workers to communicate directly with each other - all communication goes via the master.
You can pretty easily use the 'message' mechanism to set up a system for using the master as a relay to route messages between workers. I'm not sure quite what you're trying to achieve, but here's an overview.
The idea of the first chunk of code that you posted is that you run it in the master and it means that the "messageHandler" callback is called when the master receives a message from any of its workers. In fact, you can achieve the same thing by doing:
cluster.on('message', messageHandler);
The messageHandler function is passed a parameter identifying which worker sent the message.
Similarly, workers can listen for messages from the master by doing this:
process.on('message', workerMessageHandler);
You can send messages by doing:
// Worker: send message to master
process.send({ cmd: 'notifyRequest', data: 'somedata' });
// Master: send message to specific workers
worker.send({ data: 'somedata' });
cluster.workers[0].send({ whatever: 'something' });
The message can be a string or an object (which will be serialized to JSON).
Related
I'm making a server script and, to make it easier for both hosts and clients to do what they want, I made a customizable server script that runs using nw.js(with a visual interface). Said script was made using web workers since nw.js was having problems with support to worker threads.
Now that NW.js fixed their problems with worker threads, I've been trying to move all the things that were inside the web workers to worker threads, but there's a problem: When the main thread receives the answer from the second thread, the later stops responding to any subsequent message.
For example, running the following code with either NW.js or Node.js itself will return "pong" only once
const { Worker } = require('worker_threads');
const worker = new Worker('const { parentPort } = require("worker_threads");parentPort.once("message",message => parentPort.postMessage({ pong: message })); ', { eval: true });
worker.on('message', message => console.log(message));
worker.postMessage('ping');
worker.postMessage('ping');
How do I configure the worker so it will keep responding to whatever message it receives after the first one?
Because you use EventEmitter.once() method. According to the documentation this method does the next:
Adds a one-time listener function for the event named eventName. The
next time eventName is triggered, this listener is removed and then
invoked.
If you need your worker to process more than one event then use EventEmitter.on()
const worker = new Worker('const { parentPort } = require("worker_threads");' +
'parentPort.on("message",message => parentPort.postMessage({ pong: message }));',
{ eval: true });
I'm using Nodejs cluster module to have multiple workers running.
I created a basic Architecture where there will be a single MASTER process which is basically an express server handling multiple requests and the main task of MASTER would be writing incoming data from requests into a REDIS instance. Other workers(numOfCPUs - 1) will be non-master i.e. they won't be handling any request as they are just the consumers. I have two features namely ABC and DEF. I distributed the non-master workers evenly across features via assigning them type.
For eg: on a 8-core machine:
1 will be MASTER instance handling request via express server
Remaining (8 - 1 = 7) will be distributed evenly. 4 to feature:ABD and 3 to fetaure:DEF.
non-master workers are basically consumers i.e. they read from REDIS in which only MASTER worker can write data.
Here's the code for the same:
if (cluster.isMaster) {
// Fork workers.
for (let i = 0; i < numCPUs - 1; i++) {
ClusteringUtil.forkNewClusterWithAutoTypeBalancing();
}
cluster.on('exit', function(worker) {
console.log(`Worker ${worker.process.pid}::type(${worker.type}) died`);
ClusteringUtil.removeWorkerFromList(worker.type);
ClusteringUtil.forkNewClusterWithAutoTypeBalancing();
});
// Start consuming on server-start
ABCConsumer.start();
DEFConsumer.start();
console.log(`Master running with process-id: ${process.pid}`);
} else {
console.log('CLUSTER type', cluster.worker.process.env.type, 'running on', process.pid);
if (
cluster.worker.process.env &&
cluster.worker.process.env.type &&
cluster.worker.process.env.type === ServerTypeEnum.EXPRESS
) {
// worker for handling requests
app.use(express.json());
...
}
{
Everything works fine except consumers reading from REDIS.
Since there are multiple consumers of a particular feature, each one reads the same message and start processing individually, which is what I don't want. If there are 4 consumers, 1 is marked as busy and can not consumer until free, 3 are available. Once the message for that particular feature is written in REDIS by MASTER, the problem is all 3 available consumers of that feature start consuming. This means that the for a single message, the job is done based on number of available consumers.
const stringifedData = JSON.stringify(req.body);
const key = uuidv1();
const asyncHsetRes = await asyncHset(type, key, stringifedData);
if (asyncHsetRes) {
await asyncRpush(FeatureKeyEnum.REDIS.ABC_MESSAGE_QUEUE, key);
res.send({ status: 'success', message: 'Added to processing queue' });
} else {
res.send({ error: 'failure', message: 'Something went wrong in adding to queue' });
}
Consumer simply accepts messages and stop when it is busy
module.exports.startHeartbeat = startHeartbeat = async function(config = {}) {
if (!config || !config.type || !config.listKey) {
return;
}
heartbeatIntervalObj[config.type] = setInterval(async () => {
await asyncLindex(config.listKey, -1).then(async res => {
if (res) {
await getFreeWorkerAndDoJob(res, config);
stopHeartbeat(config);
}
});
}, HEARTBEAT_INTERVAL);
};
Ideally, a message should be read by only one consumer of that particular feature. After consuming, it is marked as busy so it won't consume further until free(I have handled this). Next message could only be processed by only one consumer out of other available consumers.
Please help me in tacking this problem. Again, I want one message to be read by only one free consumer and rest free consumers should wait for new message.
Thanks
I'm not sure I fully get your Redis consumers architecture, but I feel like it contradicts with the use case of Redis itself. What you're trying to achieve is essentially a queue based messaging with an ability to commit a message once its done.
Redis has its own pub/sub feature, but it is built on fire and forget principle. It doesn't distinguish between consumers - it just sends the data to all of them, assuming that its their logic to handle the incoming data.
I recommend to you use Queue Servers like RabbitMQ. You can achieve your goal with some features that AMQP 0-9-1 supports: message acknowledgment, consumer's prefetch count and so on. You can set up your cluster with very agile configs like ok, I want to have X consumers, and each can handle 1 unique (!) message at a time and they will receive new ones only after they let the server (rabbitmq) know that they successfully finished message processing. This is highly configurable and robust.
However, if you want to go serverless with some fully managed service so that you don't provision like virtual machines or anything else to run a message queue server of your choice, you can use AWS SQS. It has pretty much similar API and features list.
Hope it helps!
I want to know if it is possible to have only one node in a cluster environement for our Liferay 7.1 in localhost.
Here is a part of my portal-ext :
cluster.link.enabled=true
cluster.link.channel.properties.control=/custom_jgroups/tcp.xml
cluster.link.channel.properties.transport.0=/custom_jgroups/tcp.xml
I set following properties on VM Options for my tomcat :
-Djgroups.bind_addr=127.0.0.1
-Djgroups.tcpping.initial_hosts=127.0.0.1[7800]
In a portlet I do to execute manually a job :
Message message = new Message();
message.put(SchedulerEngine.JOB_NAME, job);
message.put(SchedulerEngine.GROUP_NAME , job);
message.put(SchedulerEngine.DESTINATION_NAME, DestinationNames.SCHEDULER_DISPATCH);
message.setDestinationName(DestinationNames.SCHEDULER_DISPATCH);
log.info("Stream " + streamNumber + " is launched ("+job+")");
try {
ClusterLinkUtil.sendMulticastMessage(message, Priority.LEVEL10);
} catch (Exception e){
log.error(e);
}
In production with two nodes in my cluster it works but in test with one node, nothing happens.
Here is the log debug. It appears that message is sent but not received ... Any ideas ?
DEBUG [http-nio-8080-exec-6][ClusterLinkImpl:131] Select channel number 0 for priority LEVEL10
DEBUG [http-nio-8080-exec-6][JGroupsClusterChannel:171] Send multicast message {destinationName=liferay/scheduler_dispatch, response=null, responseDestinationName=null, responseId=null, payload=null, values={GROUP_NAME=com.job.UselessDataPurgeJob, DESTINATION_NAME=liferay/scheduler_dispatch, JOB_NAME=com.job.UselessDataPurgeJob}}
I'm trying to create a new node for Node-Red. Basically it is a udp listening socket that shall be established via a config node and which shall pass all incoming messages to dedicated nodes for processing.
This is the basic what I have:
function udpServer(n) {
RED.nodes.createNode(this, n);
this.addr = n.host;
this.port = n.port;
var node = this;
var socket = dgram.createSocket('udp4');
socket.on('listening', function () {
var address = socket.address();
logInfo('UDP Server listening on ' + address.address + ":" + address.port);
});
socket.on('message', function (message, remote) {
var bb = new ByteBuffer.fromBinary(message,1,0);
var CoEdata = decodeCoE(bb);
if (CoEdata.type == 'digital') { //handle digital output
// pass to digital handling node
}
else if (CoEdata.type == 'analogue'){ //handle analogue output
// pass to analogue handling node
}
});
socket.on("error", function (err) {
logError("Socket error: " + err);
socket.close();
});
socket.bind({
address: node.addr,
port: node.port,
exclusive: true
});
node.on("close", function(done) {
socket.close();
});
}
RED.nodes.registerType("myServernode", udpServer);
For the processing node:
function ProcessAnalog(n) {
RED.nodes.createNode(this, n);
var node = this;
this.serverConfig = RED.nodes.getNode(this.server);
this.channel = n.channel;
// how do I get the server's message here?
}
RED.nodes.registerType("process-analogue-in", ProcessAnalog);
I can't figure out how to pass the messages that the socket receives to a variable number of processing nodes, i.e. multiple processing nodes shall share on server instance.
==== EDIT for more clarity =====
I want to develop a new set of nodes:
One Server Node:
Uses a config-node to create an UDP listening socket
Managing the socket connection (close events, error etc)
Receives data packages with one to many channels of different data
One to many processing nodes
The processing nodes shall share the same connection that the Server Node has established
The processing nodes shall handle the messages that the server is emitting
Possibly the Node-Red flow would use as many processing Nodes as there are channels in the server's data package
To quote the Node-Red documentation on config-nodes:
A common use of config nodes is to represent a shared connection to a
remote system. In that instance, the config node may also be
responsible for creating the connection and making it available to the
nodes that use the config node. In such cases, the config node should
also handle the close event to disconnect when the node is stopped.
As far as I understood this, I make the connection available via this.serverConfig = RED.nodes.getNode(this.server); but I cannot figure out how to pass data, which is received by this connection, to the node that is using this connection.
A node has no knowledge of what nodes it is connected to downstream.
The best you can do from the first node is to have 2 outputs and to send digital to one and analogue to the other.
You would do this by passing an array to the node.send() function.
E.g.
//this sends output to just the first output
node.sent([msg,null]);
//this sends output to just the second output
node.send([null,msg]);
Nodes that have receive messagess need to add a listener for input
e.g.
node.on('input', function(msg) {
...
});
All of this is well documented on the Node-RED page
The other option is if the udpServer node is a config node then you need to implement your own listeners, best bet is to look something like the MQTT nodes in core for examples of pooling connections
Code sample :
var cpuCoreLength = require("os").cpus().length;
var cluster = require("cluster");
if(cluster.isMaster){
cluster.setupMaster({
exec : "child.js"
});
var JOB_QUEUE=["data1","data2","data3"]; //for this post, value inserted manually.
for(var i=0;i<cpuCoreLength;i++){
var worker = cluster.fork();
worker.on("message",function(data){
if(data.type == "SEND_DATA"){
worker.send({"data":JOB_QUEUE[0]});
}
});
}
}
else
{
process.send("type":"SEND_DATA"});
process.on("message",function(data){
//Do the Process
});
}
Hi this is sample code for my process.In the above code JOB_QUEUE is data that to be processed by worker. when new worker created one data will be pushed to worker. Worker will use common file specified in the Master Setting. This is one copy of Master. I am going to create like 5 Master Code with different JOB_QUEUE but same child file. bcoz every master node going to handle different set of data and each master to be processing with 1000000 of data. To monitor data processed by worker purpose i created separate Master. My question, will it lead any performance Issue in Terms of CPU or due to CPU core if i run Multiple Master Node?