Manager.prototype.createClient = function () { var self = this; var host = this.options.host || 'localhost'; var port = this.options.port || 6379; var options = this.options.options || {}; check(port).isInt(); var client = new redis.createClient(port, host, options); if (typeof options.auth == 'string') { client.auth(options.auth); } client.on('error', function (err) { console.log('[' + self.id + '] ' + err); }); client.on('ready', function () { console.log('[' + self.id + '] connected to redis!'); self.emit('ready'); self.retried = 0; }); client.on('end', function () { self.emit('end'); console.log('[' + self.id + '] disconnected from redis'); }); return client; };
IpcRedisClient.prototype.initClient = function(name, host, port) { var opts = this.options[name] || this.options; host = String(host).split(":"); // For reconnect or failover to work need retry policy if (opts.servers.length > 1 || this.sentinel) { var connect_timeout = lib.toNumber(opts.connect_timeout, { min: 60000, dflt: 86400000 }); var retry_max_delay = lib.toNumber(opts.retry_max_delay, { min: 100, dflt: 60000 }); var max_attempts = lib.toNumber(opts.max_attempts, { min: 1, dflt: 10 }); var self = this; opts.retry_strategy = function(options) { logger.dev("initClient:", options); if (options.total_retry_time > connect_timeout || (lib.isArray(opts.servers) && options.attempt > max_attempts)) { setTimeout(self.onError.bind(self, self.name, { code: 'CONNECTION_BROKEN', message: opts.error && opts.error.message }), 100); return undefined; } return Math.min(100, Math.min((options.attempt % max_attempts) * 200, retry_max_delay)); } } var Redis = require("redis"); var client = new Redis.createClient(host[1] || port || opts.port || 6379, host[0] || "127.0.0.1", opts); client.on("error", this.onError.bind(this, name)); switch (name) { case "sentinel": client.on('pmessage', this.onSentinelMessage.bind(this)); client.on("ready", this.onSentinelConnect.bind(this)); break; default: client.on("ready", this.emit.bind(this, "ready")); client.on("message", this.onMessage.bind(this, name)); client.on("connect", this.onConnect.bind(this, name)); } if (this[name]) this[name].end(true); this[name] = client; logger.debug("initClient:", name, this.url, "connecting:", host, port); }
/* options includes: - host - port - masterOptions - masterName - logger (e.g. winston) - debug (boolean) */ function RedisSentinelClient(options) { // RedisClient takes (stream,options). we don't want stream, make sure only one. if (arguments.length > 1) { throw new Error("Sentinel client takes only options to initialize"); } // make this an EventEmitter (also below) events.EventEmitter.call(this); var self = this; this.options = options = options || {}; this.options.masterName = this.options.masterName || 'mymaster'; self.emitMasterErrors = false; // no socket support for now (b/c need multiple connections). if (options.port == null || options.host == null) { throw new Error("Sentinel client needs a host and port"); } // if debugging is enabled for sentinel client, enable master client's too. // (standard client just uses console.log, not the 'logger' passed here.) if (options.debug) { RedisSingleClient.debug_mode = true; } var masterOptions = options.masterOptions || {}; masterOptions.disable_flush = true; // Disables flush_and_error, to preserve queue // if master & slaves need a password to authenticate, // pass it in as 'master_auth_pass'. // (corresponds w/ 'auth_pass' for normal client, // but differentiating b/c we're not authenticating to the *sentinel*, rather to the master/slaves.) // by setting it to 'auth_pass' on master client, it should authenticate to the master (& slaves on failover). // note, sentinel daemon's conf needs to know this same password too/separately. masterOptions.auth_pass = options.master_auth_pass || null; // this client will always be connected to the active master. // using 9999 as initial; expected to fail; is replaced & re-connected to real port later. self.activeMasterClient = new RedisSingleClient.createClient(9999, '127.0.0.1', masterOptions); // pass up errors // @todo emit a separate 'master error' event? self.activeMasterClient.on('error', function(error){ if (self.emitMasterErrors) { error.message = self.myName + " master error: " + error.message; self.onError.call(self, error); } }); // pass up messages self.activeMasterClient.on('message', function(channel, message){ self.emit('message', channel, message); }); // pass up pmessage self.activeMasterClient.on('pmessage', function(pattern, channel, message){ self.emit('pmessage', pattern, channel, message); }); // pass these through ['unsubscribe','end', 'reconnecting'].forEach(function(staticProp){ // @todo rewrite this to use `apply` self.activeMasterClient.on(staticProp, function(a, b, c, d){ self.emit(staticProp, a, b, c, d); }); }); // used for logging & errors this.myName = 'sentinel-' + this.options.host + ':' + this.options.port + '-' + this.options.masterName; /* what a failover looks like: - master fires ECONNREFUSED errors a few times - sentinel listener gets: +sdown +odown +failover-triggered +failover-state-wait-start +failover-state-select-slave +selected-slave +failover-state-send-slaveof-noone +failover-state-wait-promotion +promoted-slave +failover-state-reconf-slaves +slave-reconf-sent +slave-reconf-inprog +slave-reconf-done +failover-end +switch-master (see docs @ http://redis.io/topics/sentinel) note, these messages don't specify WHICH master is down. so if a sentinel is listening to multiple masters, and we have a RedisSentinelClient for each sentinel:master relationship, every client will be notified of every master's failovers. But that's fine, b/c reconnect() checks if it actually changed, and does nothing if not. */ // one client to query ('talker'), one client to subscribe ('listener'). // these are standard redis clients. // talker is used by reconnect() below this.sentinelTalker = new RedisSingleClient.createClient(options.port, options.host); this.sentinelTalker.on('connect', function(){ self.debug('connected to sentinel talker'); }); this.sentinelTalker.on('error', function(error){ error.message = self.myName + " talker error: " + error.message; self.onError.call(self, error); }); this.sentinelTalker.on('end', function(){ self.debug('sentinel talker disconnected'); // @todo emit something? // @todo does it automatically reconnect? (supposed to) }); var sentinelListener = new RedisSingleClient.createClient(options.port, options.host); sentinelListener.on('connect', function(){ self.debug('connected to sentinel listener'); }); sentinelListener.on('error', function(error){ error.message = self.myName + " listener error: " + error.message; self.onError(error); }); sentinelListener.on('end', function(){ self.debug('sentinel listener disconnected'); // @todo emit something? }); // Connect on load this.reconnect(); // Subscribe to all messages sentinelListener.psubscribe('*'); sentinelListener.on('pmessage', function(channel, msg) { self.debug('sentinel message', msg); // pass up, in case app wants to respond self.emit('sentinel message', msg); switch(msg) { case '+sdown': self.debug('Down detected'); self.emit('down-start'); self.emitMasterErrors = false; break; case '+failover-triggered': self.debug('Failover detected'); self.emit('failover-start'); break; case '+switch-master': self.debug('Reconnect triggered by ' + msg); self.emit('failover-end'); self.reconnect(); break; } }); }
'use strict'; var express = require('express'), router = express.Router(), redis = require('redis'), menu = require('../menu'), debug = require('debug')('app:awesome'), os = require("os"); var port = process.env.REDIS_PORT || 6379; var host = process.env.REDIS_HOST || 'localhost'; var redisCache = new redis.createClient(port,host, {'max_attempts':3}); redisCache.on("error", function (err) { console.error("Awesome v2 Error " + err); redisCache = null; }); router.get('/', function(req, res) { var data = { menu: menu('awesome_v2'), layout: "template" }; res.render('awesome_v2',data); }); router.get('/app', function(req, res) { var data = { layout: "template_app", hostname: os.hostname() };