Merge pull request #974 from coralproject/redis-cluster-support

Redis Cluster Support
This commit is contained in:
Kim Gardner
2017-09-28 15:44:02 +01:00
committed by GitHub
3 changed files with 64 additions and 50 deletions
+30 -7
View File
@@ -94,14 +94,17 @@ const CONFIG = {
MONGO_URL: process.env.TALK_MONGO_URL,
REDIS_URL: process.env.TALK_REDIS_URL,
// REDIS_RECONNECTION_MAX_ATTEMPTS is the amount of attempts that a redis
// connection will attempt to reconnect before aborting with an error.
REDIS_RECONNECTION_MAX_ATTEMPTS: parseInt(process.env.TALK_REDIS_RECONNECTION_MAX_ATTEMPTS || '100'),
// REDIS_CLIENT_CONFIG is the optional configuration that is merged with the
// function config to provide deep control of the redis connection beheviour.
REDIS_CLIENT_CONFIG: process.env.TALK_REDIS_CLIENT_CONFIGURATION || '{}',
// REDIS_RECONNECTION_MAX_RETRY_TIME is the time in string format for the
// maximum amount of time that a client can be considered "connecting" before
// attempts at reconnection are aborted with an error.
REDIS_RECONNECTION_MAX_RETRY_TIME: ms(process.env.TALK_REDIS_RECONNECTION_MAX_RETRY_TIME || '1 min'),
// REDIS_CLUSTER_MODE allows configuration on the type of cluster mode enabled
// on the redis client. Can be either `NONE` or `CLUSTER`.
REDIS_CLUSTER_MODE: process.env.TALK_REDIS_CLUSTER_MODE || 'NONE',
// REDIS_CLUSTER_CONFIGURATION contains the json string for the redis cluster
// configuration.
REDIS_CLUSTER_CONFIGURATION: process.env.TALK_REDIS_CLUSTER_CONFIGURATION || '[]',
// REDIS_RECONNECTION_BACKOFF_FACTOR is the factor that will be multiplied
// against the current attempt count inbetween attempts to connect to redis.
@@ -246,6 +249,26 @@ if (process.env.NODE_ENV === 'test' && !CONFIG.REDIS_URL) {
CONFIG.REDIS_URL = 'redis://localhost/1';
}
// REDIS_CLUSTER_CONFIGURATION should be parsed when the cluster mode !== none.
if (CONFIG.REDIS_CLUSTER_MODE === 'CLUSTER') {
try {
CONFIG.REDIS_CLUSTER_CONFIGURATION = JSON.parse(CONFIG.REDIS_CLUSTER_CONFIGURATION);
} catch (err) {
throw new Error('TALK_REDIS_CLUSTER_CONFIGURATION is not valid JSON, see https://github.com/luin/ioredis#cluster for valid syntax of the list of cluster nodes');
}
if (!Array.isArray(CONFIG.REDIS_CLUSTER_CONFIGURATION)) {
throw new Error('TALK_REDIS_CLUSTER_MODE is CLUSTER, but the TALK_REDIS_CLUSTER_CONFIGURATION is invalid, see https://github.com/luin/ioredis#cluster for valid syntax of the list of cluster nodes');
}
if (CONFIG.REDIS_CLUSTER_CONFIGURATION.length === 0) {
throw new Error('TALK_REDIS_CLUSTER_CONFIGURATION must have at least one node specified in the cluster, see https://github.com/luin/ioredis#cluster for valid syntax of the list of cluster nodes');
}
}
// Client config is a JSON encoded string, defaulting to `{}`.
CONFIG.REDIS_CLIENT_CONFIG = JSON.parse(CONFIG.REDIS_CLIENT_CONFIG);
//------------------------------------------------------------------------------
// Recaptcha configuration
//------------------------------------------------------------------------------
+11 -8
View File
@@ -55,18 +55,21 @@ These are only used during the webpack build.
#### Advanced
{:.no_toc}
- `TALK_REDIS_RECONNECTION_MAX_ATTEMPTS` (_optional_) - the amount of attempts
that a redis connection will attempt to reconnect before aborting with an
error. (Default `100`)
- `TALK_REDIS_RECONNECTION_MAX_RETRY_TIME` (_optional_) - the time in string
format for the maximum amount of time that a client can be considered
"connecting" before attempts at reconnection are aborted with an error.
(Default `1 min`)
- `TALK_REDIS_CLIENT_CONFIG` (_optional_) - configuration overrides for the
redis client configuration in a JSON encoded string. Configuration is
overridden as the second parameter to the redis client constructor, and is
merged with default configuration. (Default `{}`)
- `TALK_REDIS_RECONNECTION_BACKOFF_FACTOR` (_optional_) - the time factor that
will be multiplied against the current attempt count inbetween attempts to
connect to redis. (Default `500 ms`)
- `TALK_REDIS_RECONNECTION_BACKOFF_MINIMUM_TIME` (_optional_) - the minimum time
used to delay before attempting to reconnect to redis. (Default `1 sec`)
- `TALK_REDIS_CLUSTER_MODE` (_optional_) - the cluster mode of the redis client.
Can be either `NONE` or `CLUSTER`. (Default `NONE`)
- `TALK_REDIS_CLUSTER_CONFIGURATION` (_optional_) - the json serialized form of
the cluster nodes. Only required when `TALK_REDIS_CLUSTER_MODE=CLUSTER`. See
https://github.com/luin/ioredis#cluster for configuration details.
(Default `[]`)
### Server
@@ -137,7 +140,7 @@ is not needed in most situations.
use to set a cookie containing a JWT that was issued by Talk.
(Default `process.env.TALK_JWT_COOKIE_NAME`)
- `TALK_JWT_COOKIE_NAMES` (_optional_) - the different cookie names to check for
a JWT token in, seperated by `,`. By default, we always use the
a JWT token in, separated by `,`. By default, we always use the
`process.env.TALK_JWT_COOKIE_NAME` and `process.env.TALK_JWT_SIGNING_COOKIE_NAME`
for this value. Any additional cookie names specified here will be appended to
the list of cookie names to inspect.
+23 -35
View File
@@ -1,12 +1,14 @@
const Redis = require('ioredis');
const merge = require('lodash/merge');
const debug = require('debug')('talk:services:redis');
const enabled = require('debug').enabled('talk:services:redis');
const {
REDIS_URL,
REDIS_RECONNECTION_MAX_ATTEMPTS,
REDIS_RECONNECTION_MAX_RETRY_TIME,
REDIS_RECONNECTION_BACKOFF_FACTOR,
REDIS_RECONNECTION_BACKOFF_MINIMUM_TIME,
REDIS_CLIENT_CONFIG,
REDIS_CLUSTER_MODE,
REDIS_CLUSTER_CONFIGURATION,
} = require('../config');
const attachMonitors = (client) => {
@@ -16,8 +18,8 @@ const attachMonitors = (client) => {
if (enabled) {
client.on('connect', () => debug('client connected'));
client.on('ready', () => debug('client ready'));
client.on('reconnecting', () => debug('client connection lost, attempting to reconnect'));
client.on('close', () => debug('client closed the connection'));
client.on('reconnecting', () => debug('client connection lost, attempting to reconnect'));
client.on('end', () => debug('client ended'));
}
@@ -27,44 +29,30 @@ const attachMonitors = (client) => {
console.error('Error connecting to redis:', err);
}
});
client.on('node error', (err) => debug('node error', err));
};
const connectionOptions = {
retry_strategy: function(options) {
if (options.error && options.error.code !== 'ECONNREFUSED') {
function retryStrategy(times) {
const delay = Math.max(times * REDIS_RECONNECTION_BACKOFF_FACTOR, REDIS_RECONNECTION_BACKOFF_MINIMUM_TIME);
debug('retry strategy: none, an error occured');
debug(`retry strategy: try to reconnect ${delay} ms from now`);
// End reconnecting on a specific error and flush all commands with a individual error
return options.error;
}
if (options.total_retry_time > REDIS_RECONNECTION_MAX_RETRY_TIME) {
debug('retry strategy: none, exhausted retry time');
// End reconnecting after a specific timeout and flush all commands with a individual error
return new Error('Retry time exhausted');
}
if (options.attempt > REDIS_RECONNECTION_MAX_ATTEMPTS) {
debug('retry strategy: none, exhausted retry attempts');
// End reconnecting with built in error
return undefined;
}
// reconnect after
const delay = Math.max(options.attempt * REDIS_RECONNECTION_BACKOFF_FACTOR, REDIS_RECONNECTION_BACKOFF_MINIMUM_TIME);
debug(`retry strategy: try to reconnect ${delay} ms from now`);
return delay;
}
};
return delay;
}
const createClient = () => {
let client = new Redis(REDIS_URL, connectionOptions);
let client;
if (REDIS_CLUSTER_MODE === 'NONE') {
client = new Redis(REDIS_URL, merge({}, REDIS_CLIENT_CONFIG, {
retryStrategy,
}));
} else if (REDIS_CLUSTER_MODE === 'CLUSTER') {
client = new Redis.Cluster(REDIS_CLUSTER_CONFIGURATION, merge({
scaleReads: 'slave',
}, REDIS_CLIENT_CONFIG, {
clusterRetryStrategy: retryStrategy,
}));
}
// Attach the monitors that will print debug messages to the console.
attachMonitors(client);