Skip to content

Instantly share code, notes, and snippets.

@bmeck
Created April 3, 2012 15:02
Show Gist options
  • Star 13 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save bmeck/2292729 to your computer and use it in GitHub Desktop.
Save bmeck/2292729 to your computer and use it in GitHub Desktop.
Simple Vertical Load Balancing for TCP in Node.js

About

This is the most basic example of vertical scaling by transferring connections to workers, rather than sharing server sockets. It was presented at JSConf US 2012. With this you can create some interesting balancing algorithms that cluster would defer to internal/OS logic.

Exercises

A good first project when working with this is to implement sticky sessions based upon connection.remoteAddress and/or connection.remotePort.

A good advanced project is to change from a TCP based balancer to a HTTP(S) one and balance based upon not just connection.remoteAddress etc. but also on the protocol and/or Host header.

NOTE

Browsers will try to reuse connections if possible, use curl etc. for easier testing.

#!/usr/bin/env node
//
// Simple Vertical load balancer
//
// Environmental Variables
// WORKERS - Number of workers
// PORT - Port to listen on
//
var net = require('net'),
conn_id = 0,
fork = require('child_process').fork,
worker_count = process.env.WORKERS || 3,
workers = [],
current_worker = 0;
//
// Our Load Balancer for Vertical Scaling
// Can implement IP hashes etc. for sticky sessions,
// More if you want to do packet analysis and then replay in a worker
//
var server = net.createServer(function (conn) {
//
// Pause the stream before we send it over
//
conn.pause();
var id = conn_id++;
//
// Grab a new worker
//
var conn_worker = workers.shift();
workers.push(conn_worker);
//
// Simple logging to show where it went
//
console.log('new connection going to', conn_worker.pid);
//
// Hand over the connection using sendHandle
//
conn_worker.send({
type: 'connection',
connection: id
}, conn._handle);
});
server.listen(process.env.PORT || 9090);
//
// Spawn up our workers and notify them of what they are supposed to look like
//
for(var i = 0; i < worker_count; i++) {
(workers[i] = fork('worker.js')).send({
type: 'configure',
address: server.address()
});
}
#!/usr/bin/env node
//
// Simple transparent vertical scaling server
//
//
// Application code
//
var http = require('http');
var server = http.createServer(function (req, res) {
res.end('yoyo! from PID = ' + process.pid);
console.error('response sent from', process.pid);
});
//
// Balancer glue to allow connections from the master
//
var net = require('net');
//
// Properly connect a socket to a server
// Lifted from net.js in node/lib
//
function onconnection(clientHandle) {
var self = this;
if (!clientHandle) {
return null;
}
if (self.maxConnections && self.connections >= self.maxConnections) {
clientHandle.close();
return null;
}
var socket = new net.Socket({
handle: clientHandle,
allowHalfOpen: self.allowHalfOpen
});
socket.readable = socket.writable = true;
socket.resume();
self.connections++;
socket.server = self;
self.emit('connection', socket);
socket.emit('connect');
return socket;
}
//
// Handle configuration and connection acceptance
//
process.on('message', function setupConnections(msg, stream) {
switch (msg.type) {
case 'configure':
//
// Want the server to copy out the address
//
var address = msg.address;
server.address = function address() {
return address;
};
return;
case 'connection':
var id = msg.connection;
var socket = onconnection.call(server, stream);
//
// Let the balancer know you are actually handling this
// Could add a TTL for workers to handle things, etc.
//
if (socket) {
process.send({
type: 'connected',
connection: id
});
}
return;
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment