rocket.chat.mqtt
Version:
It's a MQTT Server, using redis to scale horizontally.
254 lines (226 loc) • 7.8 kB
JavaScript
;
var _ = require('../utils/lodash');
var util = require('util');
var net = require('net');
var utils = require('../utils');
var Connector = require('./connector');
var debug = require('../utils/debug')('ioredis:SentinelConnector');
var Redis;
function SentinelConnector(options) {
Connector.call(this, options);
if (this.options.sentinels.length === 0) {
throw new Error('Requires at least one sentinel to connect to.');
}
if (!this.options.name) {
throw new Error('Requires the name of master.');
}
}
util.inherits(SentinelConnector, Connector);
SentinelConnector.prototype.check = function (info) {
if (info.role && this.options.role !== info.role) {
debug('role invalid, expected %s, but got %s', this.options.role, info.role);
return false;
}
return true;
};
SentinelConnector.prototype.connect = function (callback, eventEmitter) {
this.connecting = true;
this.retryAttempts = 0;
if (typeof this.currentPoint !== 'number') {
this.currentPoint = -1;
}
if (!Array.isArray(this.sentinels)) {
this.sentinels = this.options.sentinels;
}
var _this = this;
var lastError;
connectToNext();
function connectToNext() {
_this.currentPoint += 1;
if (_this.currentPoint === _this.sentinels.length) {
_this.currentPoint = -1;
var retryDelay = typeof _this.options.sentinelRetryStrategy === 'function'
? _this.options.sentinelRetryStrategy(++_this.retryAttempts)
: null;
var errorMsg = typeof retryDelay !== 'number'
? 'All sentinels are unreachable and retry is disabled.'
: 'All sentinels are unreachable. Retrying from scratch after ' + retryDelay + 'ms';
if (lastError) {
errorMsg += ' Last error: ' + lastError.message;
}
debug(errorMsg);
var error = new Error(errorMsg);
if (typeof retryDelay === 'number') {
setTimeout(connectToNext, retryDelay);
eventEmitter('error', error);
} else {
callback(error);
}
return;
}
var endpoint = _this.sentinels[_this.currentPoint];
_this.resolve(endpoint, function (err, resolved) {
if (!_this.connecting) {
callback(new Error(utils.CONNECTION_CLOSED_ERROR_MSG));
return;
}
if (resolved) {
debug('resolved: %s:%s', resolved.host, resolved.port);
_this.stream = net.createConnection(resolved);
callback(null, _this.stream);
} else {
var endpointAddress = endpoint.host + ':' + endpoint.port;
var errorMsg = err
? 'failed to connect to sentinel ' + endpointAddress + ' because ' + err.message
: 'connected to sentinel ' + endpointAddress + ' successfully, but got an invalid reply: ' + resolved;
debug(errorMsg);
eventEmitter('sentinelError', new Error(errorMsg));
if (err) {
lastError = err;
}
connectToNext();
}
});
}
};
SentinelConnector.prototype.updateSentinels = function (client, callback) {
var _this = this;
client.sentinel('sentinels', this.options.name, function (err, result) {
if (err) {
client.disconnect();
return callback(err);
}
if (Array.isArray(result)) {
for (var i = 0; i < result.length; ++i) {
var sentinel = utils.packObject(result[i]);
var flags = sentinel.flags ? sentinel.flags.split(',') : [];
if (flags.indexOf('disconnected') === -1 && sentinel.ip && sentinel.port) {
var endpoint = { host: sentinel.ip, port: parseInt(sentinel.port, 10) };
var isDuplicate = _this.sentinels.some(_.bind(isSentinelEql, null, endpoint));
if (!isDuplicate) {
debug('adding sentinel %s:%s', endpoint.host, endpoint.port);
_this.sentinels.push(endpoint);
}
}
}
debug('sentinels', _this.sentinels);
}
callback(null);
});
};
SentinelConnector.prototype.resolveMaster = function (client, callback) {
var _this = this;
client.sentinel('get-master-addr-by-name', this.options.name, function (err, result) {
if (err) {
client.disconnect();
return callback(err);
}
_this.updateSentinels(client, function (err) {
client.disconnect();
if (err) {
return callback(err);
}
callback(null, Array.isArray(result) ? { host: result[0], port: result[1] } : null);
});
});
};
SentinelConnector.prototype.resolveSlave = function (client, callback) {
var _this = this;
client.sentinel('slaves', this.options.name, function (err, result) {
client.disconnect();
if (err) {
return callback(err);
}
var selectedSlave;
if (Array.isArray(result)) {
var availableSlaves = [];
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)/)) {
availableSlaves.push(slave);
}
}
// allow the options to prefer particular slave(s)
if (_this.options.preferredSlaves) {
var preferredSlaves = _this.options.preferredSlaves;
switch (typeof preferredSlaves) {
case 'function':
// use function from options to filter preferred slave
selectedSlave = _this.options.preferredSlaves(availableSlaves);
break;
case 'object':
if (!Array.isArray(preferredSlaves)) {
preferredSlaves = [preferredSlaves];
} else {
// sort by priority
preferredSlaves.sort(function (a, b) {
// default the priority to 1
if (!a.prio) {
a.prio = 1;
}
if (!b.prio) {
b.prio = 1;
}
// lowest priority first
if (a.prio < b.prio) {
return -1;
}
if (a.prio > b.prio) {
return 1;
}
return 0;
});
}
// loop over preferred slaves and return the first match
for (var p = 0; p < preferredSlaves.length; p++) {
for (var a = 0; a < availableSlaves.length; a++) {
if (availableSlaves[a].ip === preferredSlaves[p].ip) {
if (availableSlaves[a].port === preferredSlaves[p].port) {
selectedSlave = availableSlaves[a];
break;
}
}
}
if (selectedSlave) {
break;
}
}
// if none of the preferred slaves are available, a random available slave is returned
break;
}
}
if (!selectedSlave) {
// get a random available slave
selectedSlave = _.sample(availableSlaves);
}
}
callback(null, selectedSlave ? { host: selectedSlave.ip, port: selectedSlave.port } : null);
});
};
SentinelConnector.prototype.resolve = function (endpoint, callback) {
if (typeof Redis === 'undefined') {
Redis = require('../redis');
}
var client = new Redis({
port: endpoint.port || 26379,
host: endpoint.host,
family: endpoint.family || this.options.family,
retryStrategy: null,
enableReadyCheck: false,
connectTimeout: this.options.connectTimeout,
dropBufferSupport: true
});
// ignore the errors since resolve* methods will handle them
client.on('error', noop);
if (this.options.role === 'slave') {
this.resolveSlave(client, callback);
} else {
this.resolveMaster(client, callback);
}
};
function noop() {}
function isSentinelEql(a, b) {
return ((a.host || '127.0.0.1') === (b.host || '127.0.0.1')) &&
((a.port || 26379) === (b.port || 26379));
}
module.exports = SentinelConnector;