Skip to content

Instantly share code, notes, and snippets.

@tangxinfa
Last active September 28, 2016 03:49
Show Gist options
  • Save tangxinfa/1646be8aa7a6176a3eb731a914e54518 to your computer and use it in GitHub Desktop.
Save tangxinfa/1646be8aa7a6176a3eb731a914e54518 to your computer and use it in GitHub Desktop.
ioredis-sentinel-prefer-local
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
};
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