Last active
November 7, 2016 08:06
-
-
Save tangxinfa/3361a11acf2270e8388b43bfcb25ce0e to your computer and use it in GitHub Desktop.
Connect redis with Minimum Distance First(MDF) algorithm.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var Redis = require('ioredis'), | |
merge = require('utils-merge'), | |
_ = require('lodash'), | |
os = require('os'), | |
net = require('net'), | |
util = require('util'), | |
EventEmitter = require('events').EventEmitter; | |
/** | |
* Connect redis with Minimum Distance First(MDF) algorithm. | |
* | |
* @param options options. | |
* - redis | |
* redis options | |
* - local_master | |
* optional, connect to local master if no local slave exists, default is false. | |
* - minimum_distance | |
* optional, minimum distance, default is 1. | |
* - ip_distance | |
* optional, function (ip) for calculate ip distance, default is MDFRedis.ip_distance. | |
*/ | |
function MDFRedis(options) { | |
if (!(this instanceof MDFRedis)) { | |
return new MDFRedis(arguments[0]); | |
} | |
EventEmitter.call(this); | |
this.options = { | |
redis: options.redis, | |
local_master: options.local_master || false, | |
minimum_distance: options.minimum_distance || 1, | |
ip_distance: options.ip_distance || MDFRedis.ip_distance | |
}; | |
if (this.options.redis.sentinels && this.options.redis.role == 'slave') { | |
// prefer connect to local slave. | |
this._slave = new Redis(merge({ | |
preferredSlaves: this._closest.bind(this) | |
}, this.options.redis)); | |
if (this.options.local_master) { | |
// try master if slave is remote. | |
this._slave.once('connect', this._slave_connected.bind(this)); | |
} else { | |
this._redis = this._slave; | |
} | |
} else { | |
// Minimum Distance First(MDF) algorithm only available when connect slave redis through sentinels. | |
this._redis = new Redis(this.options.redis); | |
} | |
} | |
util.inherits(MDFRedis, EventEmitter); | |
/** | |
* Get the connected redis instance. | |
* | |
* @param callback function (err, client) called when done. | |
* | |
*/ | |
MDFRedis.prototype.redis = function (callback) { | |
if (this._redis) { | |
return callback(null, this._redis); | |
} | |
var self = this; | |
this.once('redis', function () { | |
callback(null, self._redis); | |
}); | |
}; | |
/** | |
* Choose closest slave from slaves. | |
* | |
* @param slaves All available slaves. | |
* | |
* @return closed one, null if no closed slave exists. | |
*/ | |
MDFRedis.prototype._closest = function (slaves) { | |
var closest = Number.MAX_VALUE; | |
var orderedSlaves = []; | |
for(var i = 0; i < slaves.length; ++i) { | |
if (net.isIPv4(slaves[i].host)) { | |
var distance = this.options.ip_distance(slaves[i].host); | |
if (! orderedSlaves[distance]) { | |
orderedSlaves[distance] = []; | |
} | |
orderedSlaves[distance].push(slaves[i]); | |
if (distance < closest) { | |
closest = distance; | |
} | |
} | |
} | |
return closest !== Number.MAX_VALUE && closest <= this.options.minimum_distance && _.sample(orderedSlaves[closest]); | |
}; | |
/// Called when slave connected. | |
MDFRedis.prototype._slave_connected = function () { | |
if (net.isIPv4(this._slave.stream.remoteAddress)) { | |
if (this.options.ip_distance(this._slave.stream.remoteAddress) > this.options.minimum_distance) { | |
// local slave not exists, try master. | |
var options = merge({}, this.options.redis); | |
options.role = 'master'; | |
this._master = new Redis(options); | |
this._master.once('connect', this._master_connected.bind(this)); | |
return; | |
} | |
} | |
// local slave exists, use it. | |
this._redis = this._slave; | |
this.emit('redis'); | |
}; | |
/// Called when master connected. | |
MDFRedis.prototype._master_connected = function () { | |
if (net.isIPv4(this._master.stream.remoteAddress) && this.options.ip_distance(this._master.stream.remoteAddress) <= this.options.minimum_distance) { | |
// local master exists, use it. | |
this._redis = this._master; | |
this._slave.disconnect(); | |
delete this._slave; | |
} else { | |
// local master not exists, use remote slave. | |
this._redis = this._slave; | |
this._master.disconnect(); | |
delete this._master; | |
} | |
this.emit('redis'); | |
}; | |
/** | |
* Get all local network addresses. | |
* | |
* | |
* @return local network addresses. | |
*/ | |
MDFRedis.local_addresses = function () { | |
if (! MDFRedis._local_addresses) { | |
MDFRedis._local_addresses = []; | |
var interfaces = os.networkInterfaces(); | |
var keys = Object.keys(interfaces).filter(function(key) { | |
var items = interfaces[key] | |
for(var i = 0; i < items.length; ++i) { | |
if (items[i].family === 'IPv4') { | |
MDFRedis._local_addresses.push(items[i].address); | |
} | |
} | |
}); | |
} | |
return MDFRedis._local_addresses; | |
}; | |
/// Calculate ip distance. | |
MDFRedis.ip_distance = function (ip) { | |
var min_distance = 4; | |
var ip_segments = ip.split('.'); | |
var local_addresses = MDFRedis.local_addresses(); | |
for(var i = 0; i < local_addresses.length; ++i) { | |
var local_ip_segments = local_addresses[i].split('.'); | |
var j = 0; | |
while(j < 4) { | |
if (ip_segments[j] == local_ip_segments[j]) { | |
++j; | |
continue; | |
} | |
break; | |
} | |
if ((4 - j) < min_distance) { | |
min_distance = (4 - j); | |
} | |
} | |
return min_distance; | |
}; | |
module.exports = MDFRedis; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var MDFRedis = require(__dirname + '/MDFRedis.js'); | |
var redis = new MDFRedis({ | |
redis: { | |
name: 'store', | |
sentinels: [ { host:'127.0.0.1', port: 16380 }, { host:'127.0.0.1', port: 16381 }, { host:'127.0.0.1', port: 16382 } ], | |
role: 'slave', | |
db: 0 | |
}, | |
local_master: true | |
}); | |
redis.redis(function (err, client) { | |
console.log("connected"); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment