Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
## Redis KeySpace Event Notifier [![Build Status](https://travis-ci.org/iamchrismiller/redis-notifier.png)](https://travis-ci.org/iamchrismiller/redis-notifier)

IMPORTANT:
This is a fork of https://github.com/iamchrismiller/redis-notifier
Here I added support for redis sentinel.
The configuration options for the database are structured differently than in the original.
I will also create a pull request for this change on https://github.com/iamchrismiller/redis-notifier
You need to pass redis-sentinel to this if you plan to use sentinel, otherwise pass null or undefined.
TODO: need to add unit tests using sentinel.

Subscribe To Redis Keyspaced Events (v2.8.x)
Using Redis' Newly Released Keyspaced Events Feature You can now subscribe to events that the server emits
Depending on the subscription mode you subscribe with when starting the Redis Server.
Expand Down Expand Up @@ -30,9 +36,10 @@ If you are using `node_redis` pre `v0.11.0` checkout the tag `v0.1.2`

```javascript
var redis = require('redis');
var sentinel = require('redis-sentinel');// if you need sentinel, other wise just pass null
var RedisNotifier = require('redis-notifier');

var eventNotifier = new RedisNotifier(redis, {
var eventNotifier = new RedisNotifier(redis, sentinel, {
redis : { host : '127.0.0.1', port : 6379 },
expired : true,
evicted : true,
Expand Down Expand Up @@ -60,6 +67,7 @@ In lieu of a formal style-guide, take care to maintain the existing coding style

## Release History

- 0.3.0 added support for redis sentinel
- 0.2.0 updated node_redis connection args, added deinit method
- 0.1.2 updated logger interface
- 0.1.1 changed expire attribute to expired
Expand Down
95 changes: 77 additions & 18 deletions lib/RedisEventNotifier.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,41 +16,100 @@ var logAdapter = require('./adapters/logger'),
* @param options
* @constructor
*/
function RedisNotifier(redis, options) {
function RedisNotifier(redis, sentinel, options) {

this.settings = extend(true, {
redis : {
host : 'localhost',
port : 6379,
db : 0,
options : {
family : null //IPv4/IPv6
}
},
expired : true,
evicted : true,
dbConfig: {
useRedisSentinel: false,
db: 0,
redis: {
host: 'localhost',
port: 6379,
// auth_pass: 'yourpassword',
// return_buffers: true, // required if storing binary data
// retry_max_delay: 1000
},
redisSentinel: {
masterName: 'mymaster',
endPoints: [
{host: 'localhost', port: 26379},
{host: 'localhost', port: 26380},
{host: 'localhost', port: 26381}
],
options: {
// auth_pass: 'yourpassword'
// , return_buffers: true // required if storing binary data
//, connect_timeout: 10000
//, retry_max_delay: 1000
}
}
},
expired: true,
evicted: true,
logLevel : 'INFO'
}, options || {});

console.log('this.settings', this.settings);

//Set Global Log Level
logAdapter.setLogLevel(this.settings.logLevel);


//Require Redis if its not injected
if (!redis || typeof redis !== 'object') {
throw new Error("You must provide a Redis module");
}

//The Redis Subscriber Instance
logger.info("Initializing" + JSON.stringify(this.settings));

var dbConfig = this.settings.dbConfig;
logger.warn('RedisEventNotifier: dbConfig.useRedisSentinel', dbConfig.useRedisSentinel);

// Call the super EventEmitter constructor.
EventEmitter.call(this);

//Create Redis Subscriber Client
this.subscriber = redis.createClient(this.settings.redis.port, this.settings.redis.host, this.settings.redis.options);
//this.subscriber = redis.createClient(this.settings.redis.port, this.settings.redis.host, this.settings.redis.options);
//Select Appropriate Database

if (isNaN(dbConfig.db) || dbConfig.db > 15 || dbConfig.db < 0) {
throw new Error("RedisEventNotifier: You must provide db number");
}

if (!dbConfig.useRedisSentinel) {
if (!dbConfig.redis || !dbConfig.redis.host || !dbConfig.redis.port
|| dbConfig.redis.host.length == 0 || dbConfig.redis.port == 0) {
throw new Error("RedisEventNotifier: You must provide redis configuration settings");
}

//Create Redis Subscriber Client
logger.info('RedisEventNotifier: using redis. Redis config is', dbConfig.redis);
//redis.debug_mode = true;
this.subscriber = redis
.createClient(dbConfig.redis.port, dbConfig.redis.host, dbConfig.redis.options);

} else {
// instantiate sentinel client
logger.info('RedisEventNotifier: using redis SENTINEL -------- Sentinel config is', dbConfig.redisSentinel);
//sentinel.debug_mode = true;
this.subscriber = sentinel
.createClient(dbConfig.redisSentinel.endPoints, dbConfig.redisSentinel.masterName, dbConfig.redisSentinel.options);
}

// If not authenticated yet, perform authentication.
if (dbConfig.redis.auth) {
this.subscriber.auth(dbConfig.redis.auth, function(err) {
if (!err) {
logger.info('RedisEventNotifier: Redis Authenticated -----------');
} else {
// TODO: need to handle error
logger.error('RedisEventNotifier: Error authenticating redis -----------', err);
}
});
}

//Select Appropriate Database
this.subscriber.select(this.settings.redis.db);
this.subscriber.select(dbConfig.db);

//Redis Ready To Subscribe
this.subscriber.on('ready', function () {
Expand Down Expand Up @@ -124,7 +183,7 @@ RedisNotifier.prototype.parseMessageChannel = function (channel) {
* @private
*/
RedisNotifier.prototype._subscribeKeyspace = function (key) {
var subscriptionKey = "__keyspace@" + this.settings.redis.db + "__:" + key;
var subscriptionKey = "__keyspace@" + this.settings.dbConfig.db + "__:" + key;
logger.debug("Subscribing To Event " + subscriptionKey);
this.subscriber.psubscribe(subscriptionKey);
};
Expand All @@ -135,7 +194,7 @@ RedisNotifier.prototype._subscribeKeyspace = function (key) {
* @private
*/
RedisNotifier.prototype._unsubscribeKeyspace = function (key) {
var subscriptionKey = "__keyspace@" + this.settings.redis.db + "__:" + key;
var subscriptionKey = "__keyspace@" + this.settings.dbConfig.db + "__:" + key;
logger.debug("UnSubscribing From Event " + subscriptionKey);
this.subscriber.punsubscribe(subscriptionKey);
};
Expand All @@ -146,7 +205,7 @@ RedisNotifier.prototype._unsubscribeKeyspace = function (key) {
* @private
*/
RedisNotifier.prototype._subscribeKeyevent = function (key) {
var subscriptionKey = "__keyevent@" + this.settings.redis.db + "__:" + key;
var subscriptionKey = "__keyevent@" + this.settings.dbConfig.db + "__:" + key;
logger.debug("Subscribing To Event :" + subscriptionKey);
this.subscriber.psubscribe(subscriptionKey);
};
Expand All @@ -158,7 +217,7 @@ RedisNotifier.prototype._subscribeKeyevent = function (key) {
* @private
*/
RedisNotifier.prototype._unsubscribeKeyevent = function (key) {
var subscriptionKey = "__keyevent@" + this.settings.redis.db + "__:" + key;
var subscriptionKey = "__keyevent@" + this.settings.dbConfig.db + "__:" + key;
logger.debug("UnSubscribing From Event :" + subscriptionKey);
this.subscriber.punsubscribe(subscriptionKey);
};
Expand Down
9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "redis-notifier",
"version": "0.2.0",
"description": "Redis Keyspace Event Notifier",
"name": "redis-sentinel-notifier",
"version": "0.3.0",
"description": "Redis Keyspace Event Notifier witn Sentinel support",
"author": "Chris Miller",
"main": "index.js",
"scripts": {
Expand All @@ -10,13 +10,14 @@
"devDependencies": {
"grunt": "~0.4.2",
"grunt-cli": "~0.1.11",
"jasmine-node": "https://github.com/CraigCav/grunt-jasmine-node/tarball/registermultitask"
"grunt-jasmine-node": "0.3.1"
},
"dependencies": {
"extend": "~1.2.1",
"log4js": "~0.6.9",
"grunt-contrib-jshint": "~0.9.2",
"redis": "~0.11.x",
"redis-sentinel": "0.3.2",
"hiredis": "~0.1.17"
},
"keywords": [
Expand Down
40 changes: 40 additions & 0 deletions spec/mocks/redis-sentinel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*global process*/

"use strict";

//node
var EventEmitter = require('events').EventEmitter;
var util = require('util');

function RedisSentinelClient(options) {
this.options = options;

// Call the super EventEmitter constructor.
EventEmitter.call(this);

var self = this;
process.nextTick(function() {
self.emit('ready');
});
}

//Inherit EventEmitter Prototype Methods
RedisSentinelClient.prototype = Object.create( EventEmitter.prototype );

RedisSentinelClient.prototype.psubscribe = function(key) {};
RedisSentinelClient.prototype.punsubscribe = function(key) {};
RedisSentinelClient.prototype.select = function(key) {};


//Test Helper
RedisSentinelClient.prototype._triggerMessage = function(pattern, channel, expiredKey) {
this.emit("pmessage", pattern, channel, expiredKey);
};

module.exports = {

createClient : function(options) {
return new RedisSentinelClient(options);
}

};
40 changes: 35 additions & 5 deletions spec/redisEventNotifier.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,49 @@
var harness = require('./test.harness'),
RedisEventNotifier = require(harness.lib + 'RedisEventNotifier'),
redis = require('./mocks/redis'),
notifierOptions = {logLevel : 'DEBUG'};
sentinel = require('./mocks/redis-sentinel'),
notifierOptions = {
dbConfig: {
useRedisSentinel: false,
db: 0,
redis: {
host: 'localhost',
port: 6379,
// auth_pass: 'yourpassword',
// return_buffers: true, // required if storing binary data
// retry_max_delay: 1000
},
redisSentinel: {
masterName: 'mymaster',
endPoints: [
{host: 'localhost', port: 26379},
{host: 'localhost', port: 26380},
{host: 'localhost', port: 26381}
],
options: {
// auth_pass: 'yourpassword'
// , return_buffers: true // required if storing binary data
//, connect_timeout: 10000
//, retry_max_delay: 1000
}
}
},
expired: true,
evicted: true,
logLevel : 'DEBUG'
};

//Connection Test Suite
describe('RedisEventNotifier Suite', function () {

it('Should have a construct and throw an error if redis instance is not supplied', function () {
expect(function () {
new RedisEventNotifier(null, notifierOptions);
new RedisEventNotifier(null, sentinel, notifierOptions);
}).toThrow(new Error("You must provide a Redis module"));
});

it('Should evaulate the channel response correctly for parseMessageChannel', function () {
var eventNotifier = new RedisEventNotifier(redis, notifierOptions);
var eventNotifier = new RedisEventNotifier(redis, sentinel, notifierOptions);

var expiredKeyTest = eventNotifier.parseMessageChannel('__keyevent@0__:expired');
expect(expiredKeyTest.key).toBe('expired');
Expand All @@ -29,7 +59,7 @@ describe('RedisEventNotifier Suite', function () {
});

it('Should emit a "message" event when a key expires', function (done) {
var eventNotifier = new RedisEventNotifier(redis, notifierOptions);
var eventNotifier = new RedisEventNotifier(redis, sentinel, notifierOptions);

process.nextTick(function () {
//trigger expire message (test helper)
Expand All @@ -45,7 +75,7 @@ describe('RedisEventNotifier Suite', function () {
});

it('Should emit a "message" event when a key is evicted', function (done) {
var eventNotifier = new RedisEventNotifier(redis, notifierOptions);
var eventNotifier = new RedisEventNotifier(redis, sentinel, notifierOptions);

process.nextTick(function () {
//trigger expire message (test helper)
Expand Down
36 changes: 33 additions & 3 deletions spec/test.integration.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,44 @@
var harness = require('./test.harness'),
RedisEventNotifier = require(harness.lib + 'RedisEventNotifier'),
redis = require('redis'),
notifierOptions = {logLevel : 'DEBUG'};
sentinel = require('redis-sentinel'),
notifierOptions = {
dbConfig: {
useRedisSentinel: false,
db: 0,
redis: {
host: 'localhost',
port: 6379,
// auth_pass: 'yourpassword',
// return_buffers: true, // required if storing binary data
// retry_max_delay: 1000
},
redisSentinel: {
masterName: 'mymaster',
endPoints: [
{host: 'localhost', port: 26379},
{host: 'localhost', port: 26380},
{host: 'localhost', port: 26381}
],
options: {
// auth_pass: 'yourpassword'
// , return_buffers: true // required if storing binary data
//, connect_timeout: 10000
//, retry_max_delay: 1000
}
}
},
expired: true,
evicted: true,
logLevel : 'DEBUG'
};


//Integration Test Suite
describe('RedisEventNotifier Integration Suite', function () {

it('Should create a successful redis connection', function (done) {
var eventNotifier = new RedisEventNotifier(redis, notifierOptions);
var eventNotifier = new RedisEventNotifier(redis, sentinel, notifierOptions);

//Wait For Event Notifier To Be Ready
eventNotifier.on('ready', function () {
Expand All @@ -21,7 +51,7 @@ describe('RedisEventNotifier Integration Suite', function () {
});

it('Should Receive a __keyevent@0__:expired event upon key expire', function (done) {
var eventNotifier = new RedisEventNotifier(redis, notifierOptions);
var eventNotifier = new RedisEventNotifier(redis, sentinel, notifierOptions);

//Wait for Message Event
eventNotifier.on('message', function (pattern, channel, key) {
Expand Down