Streaming: Improve Redis connection options handling (#31623)

This commit is contained in:
Emelia Smith 2024-09-04 16:10:26 +02:00 committed by GitHub
parent 585e369e0b
commit 9ba81eae3e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 142 additions and 42 deletions

View file

@ -4,44 +4,114 @@ import { parseIntFromEnvValue } from './utils.js';
/**
* @typedef RedisConfiguration
* @property {import('ioredis').RedisOptions} redisParams
* @property {string} redisPrefix
* @property {string|undefined} redisUrl
* @property {string|undefined} namespace
* @property {string|undefined} url
* @property {import('ioredis').RedisOptions} options
*/
/**
*
* @param {NodeJS.ProcessEnv} env
* @returns {boolean}
*/
function hasSentinelConfiguration(env) {
return (
typeof env.REDIS_SENTINELS === 'string' &&
env.REDIS_SENTINELS.length > 0 &&
typeof env.REDIS_SENTINEL_MASTER === 'string' &&
env.REDIS_SENTINEL_MASTER.length > 0
);
}
/**
*
* @param {NodeJS.ProcessEnv} env
* @param {import('ioredis').SentinelConnectionOptions} commonOptions
* @returns {import('ioredis').SentinelConnectionOptions}
*/
function getSentinelConfiguration(env, commonOptions) {
const redisDatabase = parseIntFromEnvValue(env.REDIS_DB, 0, 'REDIS_DB');
const sentinelPort = parseIntFromEnvValue(env.REDIS_SENTINEL_PORT, 26379, 'REDIS_SENTINEL_PORT');
const sentinels = env.REDIS_SENTINELS.split(',').map((sentinel) => {
const [host, port] = sentinel.split(':', 2);
/** @type {import('ioredis').SentinelAddress} */
return {
host: host,
port: port ?? sentinelPort,
// Force support for both IPv6 and IPv4, by default ioredis sets this to 4,
// only allowing IPv4 connections:
// https://github.com/redis/ioredis/issues/1576
family: 0
};
});
return {
db: redisDatabase,
name: env.REDIS_SENTINEL_MASTER,
username: env.REDIS_USERNAME,
password: env.REDIS_PASSWORD,
sentinelUsername: env.REDIS_SENTINEL_USERNAME ?? env.REDIS_USERNAME,
sentinelPassword: env.REDIS_SENTINEL_PASSWORD ?? env.REDIS_PASSWORD,
sentinels,
...commonOptions,
};
}
/**
* @param {NodeJS.ProcessEnv} env the `process.env` value to read configuration from
* @returns {RedisConfiguration} configuration for the Redis connection
*/
export function configFromEnv(env) {
// ioredis *can* transparently add prefixes for us, but it doesn't *in some cases*,
// which means we can't use it. But this is something that should be looked into.
const redisPrefix = env.REDIS_NAMESPACE ? `${env.REDIS_NAMESPACE}:` : '';
const redisNamespace = env.REDIS_NAMESPACE;
// These options apply for both REDIS_URL based connections and connections
// using the other REDIS_* environment variables:
const commonOptions = {
// Force support for both IPv6 and IPv4, by default ioredis sets this to 4,
// only allowing IPv4 connections:
// https://github.com/redis/ioredis/issues/1576
family: 0
// Note: we don't use auto-prefixing of keys since this doesn't apply to
// subscribe/unsubscribe which have "channel" instead of "key" arguments
};
// If we receive REDIS_URL, don't continue parsing any other REDIS_*
// environment variables:
if (typeof env.REDIS_URL === 'string' && env.REDIS_URL.length > 0) {
return {
url: env.REDIS_URL,
options: commonOptions,
namespace: redisNamespace
};
}
// If we have configuration for Redis Sentinel mode, prefer that:
if (hasSentinelConfiguration(env)) {
return {
options: getSentinelConfiguration(env, commonOptions),
namespace: redisNamespace
};
}
// Finally, handle all the other REDIS_* environment variables:
let redisPort = parseIntFromEnvValue(env.REDIS_PORT, 6379, 'REDIS_PORT');
let redisDatabase = parseIntFromEnvValue(env.REDIS_DB, 0, 'REDIS_DB');
/** @type {import('ioredis').RedisOptions} */
const redisParams = {
host: env.REDIS_HOST || '127.0.0.1',
const options = {
host: env.REDIS_HOST ?? '127.0.0.1',
port: redisPort,
// Force support for both IPv6 and IPv4, by default ioredis sets this to 4,
// only allowing IPv4 connections:
// https://github.com/redis/ioredis/issues/1576
family: 0,
db: redisDatabase,
password: env.REDIS_PASSWORD || undefined,
username: env.REDIS_USERNAME,
password: env.REDIS_PASSWORD,
...commonOptions,
};
// redisParams.path takes precedence over host and port.
if (env.REDIS_URL && env.REDIS_URL.startsWith('unix://')) {
redisParams.path = env.REDIS_URL.slice(7);
}
return {
redisParams,
redisPrefix,
redisUrl: typeof env.REDIS_URL === 'string' ? env.REDIS_URL : undefined,
options,
namespace: redisNamespace
};
}
@ -50,13 +120,13 @@ export function configFromEnv(env) {
* @param {import('pino').Logger} logger
* @returns {Redis}
*/
export function createClient({ redisParams, redisUrl }, logger) {
export function createClient({ url, options }, logger) {
let client;
if (typeof redisUrl === 'string') {
client = new Redis(redisUrl, redisParams);
if (typeof url === 'string') {
client = new Redis(url, options);
} else {
client = new Redis(redisParams);
client = new Redis(options);
}
client.on('error', (err) => logger.error({ err }, 'Redis Client Error!'));