Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Redis Cluster #93

Closed
wants to merge 1 commit into from
Closed
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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,12 @@ The test suite expects:
- a redis server to be running on port 6379
- a redis server listenning to port 6378 and requiring a password: 'secret'
- a redis server listenning on socket `/tmp/redis.sock`
- a redis cluster contains nodes running on ports 7000 to 7005

See [.travis.yml](./.travis.yml)

```sh
docker-compose up -d
redis-server &
npm test
```
15 changes: 15 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
version: '3.5'
services:
redis_cluster:
container_name: redis_cluster
image: grokzen/redis-cluster:4.0.11
environment:
IP: '0.0.0.0'
CLUSTER_ONLY: 'true'
ports:
- 7000:7000
- 7001:7001
- 7002:7002
- 7003:7003
- 7004:7004
- 7005:7005
95 changes: 95 additions & 0 deletions examples/redis-cluster.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@

'use strict';

// After starting this example load http://localhost:8080 and hit refresh, you will notice that it loads the response from cache for the first 5 seconds and then reloads the cache

// Load modules

const Catbox = require('catbox');
const Http = require('http');
const CatboxRedisCluster = require('..').Cluster; // require('catbox-redis').Cluster on your project



// Declare internals

const internals = {};


internals.handler = function (req, res) {

internals.getResponse((err, item) => {

if (err) {
res.writeHead(500);
res.end();
}
else {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end(item);
}
});
};


internals.getResponse = function (callback) {

const key = {
segment: 'example',
id: 'myExample'
};

const cacheValue = 'my example';
const ttl = 10000; // How long item will be cached in milliseconds

internals.client.get(key, (err, cached) => {

if (err) {
return callback(err);
}
else if (cached) {
return callback(null, 'From cache: ' + cached.item);
}

internals.client.set(key, cacheValue, ttl, (error) => {

callback(error, cacheValue);
});
});
};


internals.startCache = function (callback) {

const engine = new CatboxRedisCluster([
{
host: '127.0.0.1',
port: 7000
}
], {
redisOptions: {
password: 'secret',
partition: 'example'
}
});
internals.client = new Catbox.Client(engine);
internals.client.start(callback);
};


internals.startServer = function (err) {

if (err) {
console.log(err);
console.log('Could not connect to redis cluster. Ending process.');
process.exit();
}
else {
const server = Http.createServer(internals.handler);
server.listen(8080);
console.log('Server started at http://localhost:8080/');
}
};


internals.startCache(internals.startServer);
120 changes: 120 additions & 0 deletions lib/base.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
'use strict';

exports = module.exports = class Connection {
constructor(options) {

this.settings = Object.assign({
db: options.database || options.db,
name: options.sentinelName,
tls: options.tls,
lazyConnect: true
}, options);
}

async stop() {

try {
if (this.client && !this.settings.client) {
this.client.removeAllListeners();
await this.client.disconnect();
}
}
finally {
this.client = null;
}
}

isReady() {

return !!this.client && this.client.status === 'ready';
}

validateSegmentName(name) {

if (!name) {
return new Error('Empty string');
}

if (name.indexOf('\0') !== -1) {
return new Error('Includes null character');
}

return null;
}

async get(key) {

if (!this.client) {
throw Error('Connection not started');
}

const result = await this.client.get(this.generateKey(key));

if (!result) {
return null;
}

let envelope = null;

try {
envelope = JSON.parse(result);
}
catch (ignoreErr) { } // Handled by validation below

if (!envelope) {
throw Error('Bad envelope content');
}

if (!envelope.stored || !envelope.hasOwnProperty('item')) {
throw Error('Incorrect envelope structure');
}

return envelope;
}

async set(key, value, ttl) {

if (!this.client) {
throw Error('Connection not started');
}

const envelope = {
item: value,
stored: Date.now(),
ttl
};

const cacheKey = this.generateKey(key);
const stringifiedEnvelope = JSON.stringify(envelope);

await this.client.set(cacheKey, stringifiedEnvelope);

const ttlSec = Math.max(1, Math.floor(ttl / 1000));

// Use 'pexpire' with ttl in Redis 2.6.0
return this.client.expire(cacheKey, ttlSec);
}

async drop(key) {

if (!this.client) {
throw Error('Connection not started');
}

return await this.client.del(this.generateKey(key));
}

generateKey({ id, segment }) {

const parts = [];

if (this.settings.partition) {
parts.push(encodeURIComponent(this.settings.partition));
}

parts.push(encodeURIComponent(segment));
parts.push(encodeURIComponent(id));

return parts.join(':');
}
};
Loading