Last active
September 28, 2016 03:49
-
-
Save tangxinfa/1646be8aa7a6176a3eb731a914e54518 to your computer and use it in GitHub Desktop.
ioredis-sentinel-prefer-local
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'), | |
utils = require('ioredis/lib/utils'), | |
_ = require('lodash'), | |
net = require('net'), | |
assert = require('assert'); | |
/** | |
* A SentinelConnector.prototype.resolveSlave replacement, prefer local slave. | |
* | |
* @param client redis client. | |
* @param callback function (err, slave) called when done. | |
* slave with a extra boolean field "local_node" to indicate slave is in local network. | |
* | |
*/ | |
function resolveSlavePreferLocal (client, callback) { | |
var self = this; | |
client.sentinel('slaves', this.options.name, function (err, result) { | |
if (err) { | |
client.disconnect(); | |
return callback(err); | |
} | |
var localIP = client.stream.localAddress; | |
client.disconnect(); | |
var localIPSegments = new Array(4); | |
if (net.isIPv4(localIP)) { | |
localIPSegments = localIP.split('.'); | |
} | |
var selectedSlave; | |
var local_node = false; | |
if (Array.isArray(result)) { | |
var localSlaves = []; | |
var remoteSlaves = []; | |
for (var i = 0; i < result.length; ++i) { | |
var slave = utils.packObject(result[i]); | |
if (slave.flags && !slave.flags.match(/(disconnected|s_down|o_down)/)) { | |
if (net.isIPv4(slave.ip)) { | |
var slaveIpSegments = slave.ip.split('.'); | |
if (localIPSegments[0] === slaveIpSegments[0] && | |
localIPSegments[1] === slaveIpSegments[1] && | |
localIPSegments[2] === slaveIpSegments[2]) { | |
localSlaves.push(slave); | |
continue; | |
} | |
} | |
remoteSlaves.push(slave); | |
} | |
} | |
selectedSlave = _.sample(localSlaves.length ? localSlaves : remoteSlaves); | |
local_node = Boolean(localSlaves.length); | |
} | |
console.warn('redis(' + JSON.stringify({name: self.options.name, db: self.options.db, sentinels: self.options.sentinels}) + ') resolve slave to' + (local_node ? ' local' : '') + ': ' + selectedSlave.ip + ':' + selectedSlave.port); | |
callback(null, selectedSlave ? { host: selectedSlave.ip, port: selectedSlave.port, local_node: local_node } : null); | |
}); | |
}; | |
/** | |
* Prefer connect to local slave. | |
* | |
* @param client redis client. | |
* | |
* @return is this change successful. | |
*/ | |
function preferLocalSlave(client) { | |
if (client.options.role === 'slave' && client.connector.resolveSlave) { | |
if (client.options.lazyConnect && client.status == 'wait') { | |
client.connector.resolveSlave = resolveSlavePreferLocal; | |
return true; | |
} | |
console.warn('redis client(' + JSON.stringify({name: client.options.name, db: client.options.db, sentinels: client.options.sentinels}) + ') status unexpected'); | |
} | |
return false; | |
} | |
/** | |
* A SentinelConnector.prototype.resolveMaster replacement, indicate the resolved node is local node. | |
* | |
* @param client redis client. | |
* @param callback function (err, master) called when done. | |
* master with a extra boolean field "local_node" to indicate master is in local network. | |
* | |
*/ | |
function resolveMasterPreferLocal (client, callback) { | |
client.sentinel('get-master-addr-by-name', this.options.name, function (err, result) { | |
if (err) { | |
client.disconnect(); | |
return callback(err); | |
} | |
var localIP = client.stream.localAddress; | |
client.disconnect(); | |
var localIPSegments = new Array(4); | |
if (net.isIPv4(localIP)) { | |
localIPSegments = localIP.split('.'); | |
} | |
var local_node = false; | |
if (Array.isArray(result)) { | |
var ip = result[0]; | |
if (net.isIPv4(ip)) { | |
var ipSegments = ip.split('.'); | |
if (localIPSegments[0] === ipSegments[0] && | |
localIPSegments[1] === ipSegments[1] && | |
localIPSegments[2] === ipSegments[2]) { | |
local_node = true; | |
} | |
} | |
} | |
callback(null, Array.isArray(result) ? { host: result[0], port: result[1], local_node: local_node } : null); | |
}); | |
}; | |
/** | |
* A SentinelConnector.prototype.resolve replacement, prefer resolve to local node. | |
* | |
* @param endpoint sentinel endpoint to connect. | |
* @param callback function (err, node) called when done. | |
* node with a extra boolean field "local_node" to indicate node is in local network. | |
* | |
*/ | |
function resolvePreferLocal(endpoint, callback) { | |
assert(this.options.role === 'slave'); | |
var client = new Redis({ | |
port: endpoint.port, | |
host: endpoint.host, | |
retryStrategy: null, | |
enableReadyCheck: false, | |
connectTimeout: this.options.connectTimeout | |
}); | |
var self = this; | |
this.resolveSlave(client, function (slave_err, slave) { | |
if (slave_err || !slave ||!slave.local_node) { | |
if (slave_err) { | |
console.error('redis(' + JSON.stringify({name: self.options.name, db: self.options.db, sentinels: self.options.sentinels}) + ') resolve slave error(' + slave_err.toString() + ')'); | |
} | |
client = new Redis({ | |
port: endpoint.port, | |
host: endpoint.host, | |
retryStrategy: null, | |
enableReadyCheck: false, | |
connectTimeout: self.options.connectTimeout | |
}); | |
return self.resolveMaster(client, function (master_err, master) { | |
if (master_err) { | |
console.error('redis(' + JSON.stringify({name: self.options.name, db: self.options.db, sentinels: self.options.sentinels}) + ') resolve master error(' + master_err.toString() + ')'); | |
} | |
if (!master_err && master && master.local_node) { | |
console.warn('redis(' + JSON.stringify({name: self.options.name, db: self.options.db, sentinels: self.options.sentinels}) + ') resolve slave to local master: ' + master.host + ':' + master.port); | |
return callback(null, master); | |
} else if (slave || master) { | |
return callback(null, slave || master); | |
} | |
return callback(slave_err || master_err, null); | |
}); | |
} else { | |
return callback(null, slave); | |
} | |
}); | |
} | |
/** | |
* A SentinelConnector.prototype.check replacement, enable connect local master when connect to slave. | |
*/ | |
function checkPreferLocal(info) { | |
return true; | |
} | |
/** | |
* Prefer connect to local redis node, slave first. | |
* | |
* @param client redis client. | |
* | |
* @return is this change successful. | |
*/ | |
function preferLocal(client) { | |
if (client.options.role === 'slave' && client.connector.resolveSlave) { | |
if (client.options.lazyConnect && client.status == 'wait') { | |
if (client.connector.resolveSlave != resolveSlavePreferLocal) { | |
preferLocalSlave(client); | |
} | |
client.connector.resolveMaster = resolveMasterPreferLocal; | |
client.connector.resolve = resolvePreferLocal; | |
client.connector.check = checkPreferLocal; | |
return true; | |
} | |
console.warn('redis client(' + JSON.stringify({name: client.options.name, db: client.options.db, sentinels: client.options.sentinels}) + ') status unexpected'); | |
} | |
return false; | |
} | |
module.exports = { | |
preferLocalSlave: preferLocalSlave, | |
preferLocal: preferLocal | |
}; |
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'), | |
preferLocal = require('./ioredis_local').preferLocal; | |
// lazyConnect must be true for preferLocal works. | |
var options = {name: "data", sentinels: sentinels, db: 0, role: "slave", lazyConnect: true} | |
var client = new Redis(options); | |
if (preferLocal(client)) { | |
console.warn("prefer local on redis sentinel(" + JSON.stringify(options) + ")"); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment