PowerHouse - because power plants are scaled too.
A single generator does not make enough power to satisfy a town, but many generators and such may do.
And a single node HTTP/S server doesn't satisfy all clients - but many, will.
There is the cluster
module, yes. And it actually is really good. However - what if you want to use Socket.IO? The sticky-session
module is a cluster-system by itself. So you can't fork your app, and then use that one also - and maybe you have a cluster module that utilizes hot-reloading (restarting your server on-demand). So all in all, It can be quite a clusterf**k. I have made the same experience, and I am done trying to mangle things right, here I present you my solution: the PowerHouse! And it packs more power than you could imagine - so how about a demo?
var PowerHouse = ;var house = ; // Events. Use PowerHouse.isMaster or assign inside PowerHouse.masterifhouseisMaster house; house;
Installation
Your typical
npm install powerhouse
API
PowerHouse()
(or PowerHouse.init()
)
Configuration within Many of the options are explaiend above already, but here is the entire and full list. The object is to be seen in two separate parts; the "initial" config that is anything but the workers
property. And then the per-worker configuration.
Initial configuration
title
: Process title for the master processamount
: The standard amount of workers to spawn. Defaults to:require("os").cpus().length
verbose
: More logging than usual.master
: A callback to use when still on the master. It is called during initialization. The callback gets two parameters:config
: The initial configrun
: This function triggers the actual initialization steps. Use this if you need heavy preparations for your code (finding ports, checking fiels and config, ...). Defaults to:function(config,run){ run(); }
Per-worker config
title
: Title for your worker process. Inherited from initial if not specified.amount
: Amount of workers. Inherited from initial if not specified.reloadable
: Reload this worker on exit/crash.type
: Specify the type of worker.PowerHouse.WORKER
: This is the usualcluster.fork()
kind of worker. it is tightly bound to the parent process and automatically invokes socket sharing. You can see the NodeJS docs on thecluster
module.
PowerHouse.CHILD_PROCESS
: Run your worker in an entirely new environment - its own NodeJS instance. It can be seen as a sub-master. It does not provide socket-sharing! To use it, you have to do a little bit of setup yourself. An example is below.
isServer
: If this is turned on, then PowerHouse knows of your worker being something like a HTTP server. This option turns on sticky connections. It emits an event to your worker and sends the socket upon connection. PowerHouse provides a shim to utilize this. An example below.listen
: If the above is set to true, this is used on thenet.createServer().listen()
method as the arguments. Be aware that if you specify a listening handler here, it will only run once, on the master and will very likely not have access to some data you may want it to have access to.
Events
PowerHouse doesn't just configure workers and sit there happily, it returns an instance that you can bind events to, run methods from and more! Following are the events you can listen on - they are represented as functions for this case.
worker.start(worker)
: A worker was startedworker.stop(worker, code, signal)
: Emitted every time a worker goes down.worker.reload(worker)
: Triggered when a worker is reloaded on purpose.worker.message(message[, handle])
: This is triggered if the worker usesprocess.send()
orcluster.send()
.worker.shutdown(worker)
: Triggered when the worker is requested to shutdown.master.shutdown(reason)
: Triggered when the master process is about to go down.reason
is a string.master.reconfigure(config)
: Sent to all workers when their config was updated.
Methods
PowerHouse.init(Config)
: The equivalent of callingPowerHouse(Config)
.PowerHouse.run()
: This is therun()
function passed to the callback inInitial.master
. Yes, you don't have to actually start the clustering right there - it is up to you to leave out calling this function and waiting for something to happen before starting properly.PowerHouse.get(id)
: Get a worker by it's ID. The ID being the title of the worker.PowerHouse.set(id, config)
: re-configure the workers. Once this is done, themaster.reconfigure(config)
event is sent to the responsible workers.PowerHouse.reload([id])
: Reload all reloadable workers. That involves making them shut down and restarting them. Passing an ID only restarts this group of workers.PowerHouse.server(net compatible server)
: This server will be set up to get connections when using socket sharing.PowerHouse.isMaster()
: Check if you are on the master processPowerHouse.isWorker()
: Check if you are on the worker.
Exiting
If you want to exit from the master process and want to make sure that all your workers exit cleanly, then please use:
PowerHouse.kill()
: Runs the shutdown handlers by SIGTERM'ing itself, then cleanly exits.
Examples
Basic example with 2 files, master.js and child.js
// master.jsvar house = title: "MyApp" count: 1 workers: title: "MyApp Worker" exec: "./child.js" config: config ; // child.jsvar mailer = ;var redis = ;var sub = redis;var pub = redis;sub;pub;
HTTP server
// master.jsvar house = title: "MyApp" amount: 4 workers: title: "HTTP server" exec: "./child.js" config: port: 9999 ; // child.jsmoduleexports { var { res; }; // This function gets the config. Use it, if you want the config. // Does only work with clusters, not child_process'es. var server = ; server; // To use with express: var app = ; app; app;}
Sticky HTTP
// master.jsvar PowerHouse = ;var house = ; // child.jsvar PowerHouse = ;// Using Node HTTPvar http = ;var server = http;PowerHouse; // DO NOT .listen()! PowerHouse emits the listening event.server;// Using Expressvar app = ;var server = ;PowerHouse;// Set up your routes like usual, or use the listening event.// Express does not properly fire when using the "connection" event,// which is used by this module to distribute sockets.app;
If you want to contribute an example, just PR it. :)
Motivation
I was trying to make sticky-session
work with Express and Socket.IO, had about four separate workers that just ran fine in a cluster - but my hTTP service needed to run in a separate child. It just got frustrating that the module I originally used didn't do the job right. So here I am - a solutiont hat hopefuly will work!
Development
Works:
- Spawning sub-processes via cluster.fork() and child_process.fork().
- Basic eventing.
- Processing configuration and creating workers and internal structure.
- Determining between Master and Worker/Child process.
PowerHouse.server()
doesnt exist yet, thus no socket forwarding- Serious eventing between worker and child.
Does not work yet:
- Keeping a child_process.fork()'ed module alive beyond its execution, just in case.
PowerHouse.reload()
isn't implemented yet.