UNPKG

n8n

Version:

n8n Workflow Automation Tool

205 lines 8.76 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.RedisClientService = void 0; const backend_common_1 = require("@n8n/backend-common"); const config_1 = require("@n8n/config"); const decorators_1 = require("@n8n/decorators"); const di_1 = require("@n8n/di"); const ioredis_1 = __importDefault(require("ioredis")); const typed_emitter_1 = require("../typed-emitter"); const RECONNECT_AND_RETRY = 2; const DO_NOT_RECONNECT = false; let RedisClientService = class RedisClientService extends typed_emitter_1.TypedEmitter { constructor(logger, globalConfig) { super(); this.logger = logger; this.globalConfig = globalConfig; this.clients = new Set(); this.config = { maxTimeout: this.globalConfig.queue.bull.redis.timeoutThreshold, retryInterval: 1000, resetLength: 30_000, }; this.lostConnection = false; this.logger = this.logger.scoped(['redis', 'scaling']); this.registerListeners(); } isConnected() { return !this.lostConnection; } createClient(options) { const { retryStrategy, resetRetryState } = this.makeRetryStrategy(); const client = this.clusterNodes().length > 0 ? this.createClusterClient(options, retryStrategy) : this.createRegularClient(options, retryStrategy); client.on('error', (error) => { if ('code' in error && error.code === 'ECONNREFUSED') return; this.logger.error(`[Redis client] ${error.message}`, { error }); }); client.on('ready', () => { resetRetryState(); if (this.lostConnection) { this.emit('connection-recovered'); this.lostConnection = false; } }); this.clients.add(client); return client; } toValidPrefix(prefix) { if (this.clusterNodes().length > 0) { if (!prefix.startsWith('{')) prefix = '{' + prefix; if (!prefix.endsWith('}')) prefix += '}'; } return prefix; } createRegularClient({ type, extraOptions }, retryStrategy) { const options = this.getOptions({ extraOptions, retryStrategy }); const { host, port } = this.globalConfig.queue.bull.redis; options.host = host; options.port = port; const client = new ioredis_1.default(options); this.logger.debug(`Started Redis client ${type}`, { type, host, port }); return client; } createClusterClient({ type, extraOptions }, retryStrategy) { const options = this.getOptions({ extraOptions, retryStrategy }); const clusterNodes = this.clusterNodes(); const clusterOptions = this.getClusterOptions(options, retryStrategy); const clusterClient = new ioredis_1.default.Cluster(clusterNodes, clusterOptions); this.logger.debug(`Started Redis cluster client ${type}`, { type, clusterNodes }); return clusterClient; } getOptions({ extraOptions, retryStrategy, }) { const { username, password, db, tls, dualStack, keepAlive, keepAliveDelay, keepAliveInterval, reconnectOnFailover, } = this.globalConfig.queue.bull.redis; const options = { username, password, db, enableReadyCheck: false, maxRetriesPerRequest: null, retryStrategy, ...extraOptions, }; if (dualStack) options.family = 0; if (tls) options.tls = {}; if (keepAlive) { options.keepAlive = keepAliveDelay; options.keepAliveInterval = keepAliveInterval; } if (reconnectOnFailover) { options.reconnectOnError = (redisErr) => { const targetError = 'READONLY'; if (redisErr.message.includes(targetError)) { this.logger.warn('Reconnecting to Redis due to READONLY error (possible failover event)'); return RECONNECT_AND_RETRY; } return DO_NOT_RECONNECT; }; } return options; } getClusterOptions(options, retryStrategy) { const { slotsRefreshTimeout, slotsRefreshInterval, dnsResolveStrategy } = this.globalConfig.queue.bull.redis; const clusterOptions = { redisOptions: options, clusterRetryStrategy: retryStrategy, slotsRefreshTimeout, slotsRefreshInterval, }; if (dnsResolveStrategy === 'NONE') { clusterOptions.dnsLookup = (address, lookupFn) => lookupFn(null, address); } return clusterOptions; } makeRetryStrategy() { let lastAttemptTs = 0; let cumulativeTimeout = 0; const retryStrategy = () => { const nowTs = Date.now(); if (nowTs - lastAttemptTs > this.config.resetLength) { cumulativeTimeout = 0; lastAttemptTs = nowTs; } else { cumulativeTimeout += nowTs - lastAttemptTs; lastAttemptTs = nowTs; if (cumulativeTimeout > this.config.maxTimeout) { const maxTimeout = Math.round(this.config.maxTimeout / 1000) + 's'; this.logger.error(`Unable to connect to Redis after trying to connect for ${maxTimeout}`); this.logger.error('Exiting process due to Redis connection error'); process.exit(1); } } this.lostConnection = true; this.emit('connection-lost', cumulativeTimeout); return this.config.retryInterval; }; const resetRetryState = () => { lastAttemptTs = 0; cumulativeTimeout = 0; }; return { retryStrategy, resetRetryState }; } clusterNodes() { return this.globalConfig.queue.bull.redis.clusterNodes .split(',') .filter((pair) => pair.trim().length > 0) .map((pair) => { const [host, port] = pair.split(':'); return { host, port: parseInt(port) }; }); } emit(event, ...args) { return super.emit(event, ...args); } registerListeners() { const { maxTimeout: maxTimeoutMs, retryInterval: retryIntervalMs } = this.config; const retryInterval = this.formatTimeout(retryIntervalMs); const maxTimeout = this.formatTimeout(maxTimeoutMs); this.on('connection-lost', (cumulativeTimeoutMs) => { const cumulativeTimeout = this.formatTimeout(cumulativeTimeoutMs); const reconnectionMsg = `Trying to reconnect in ${retryInterval}...`; const timeoutDetails = `${cumulativeTimeout}/${maxTimeout}`; this.logger.warn(`Lost Redis connection. ${reconnectionMsg} (${timeoutDetails})`); }); this.on('connection-recovered', () => { this.logger.info('Recovered Redis connection'); }); } formatTimeout(timeoutMs) { const timeoutSeconds = timeoutMs / 1000; const roundedTimeout = Math.round(timeoutSeconds * 10) / 10; return roundedTimeout + 's'; } }; exports.RedisClientService = RedisClientService; __decorate([ (0, decorators_1.Debounce)(1000), __metadata("design:type", Function), __metadata("design:paramtypes", [Event, Object]), __metadata("design:returntype", Boolean) ], RedisClientService.prototype, "emit", null); exports.RedisClientService = RedisClientService = __decorate([ (0, di_1.Service)(), __metadata("design:paramtypes", [backend_common_1.Logger, config_1.GlobalConfig]) ], RedisClientService); //# sourceMappingURL=redis-client.service.js.map