redis-smq
Version:
A simple high-performance Redis message queue for Node.js.
149 lines • 8.69 kB
JavaScript
import { async, CallbackEmptyReplyError, logger, withRedisClient, } from 'redis-smq-common';
import { RedisClient } from '../../common/redis-client/redis-client.js';
import { ELuaScriptName } from '../../common/redis-client/scripts/scripts.js';
import { redisKeys } from '../../common/redis-keys/redis-keys.js';
import { Configuration } from '../../config/index.js';
import { _parseQueueParamsAndValidate } from '../queue/_/_parse-queue-params-and-validate.js';
import { EQueueProperty, Queue, } from '../queue/index.js';
import { _hasRateLimitExceeded } from './_/_has-rate-limit-exceeded.js';
import { QueueRateLimitInvalidIntervalError, QueueRateLimitInvalidLimitError, QueueRateLimitQueueNotFoundError, } from './errors/index.js';
export class QueueRateLimit {
redisClient;
logger;
queue;
constructor() {
this.logger = logger.getLogger(Configuration.getSetConfig().logger, this.constructor.name.toLowerCase());
this.logger.debug('Initializing QueueRateLimit');
this.redisClient = new RedisClient();
this.redisClient.on('error', (err) => this.logger.error(err));
this.queue = new Queue();
this.logger.debug('QueueRateLimit initialized');
}
clear(queue, cb) {
const queueName = typeof queue === 'string' ? queue : `${queue.name}@${queue.ns}`;
this.logger.debug(`Clearing rate limit for queue: ${queueName}`);
withRedisClient(this.redisClient, (client, cb) => {
this.logger.debug(`Validating queue parameters for: ${queueName}`);
async.withCallback((cb) => _parseQueueParamsAndValidate(client, queue, cb), (queueParams, cb) => {
const { keyQueueProperties, keyQueueRateLimitCounter } = redisKeys.getQueueKeys(queueParams, null);
this.logger.debug(`Clearing rate limit for queue ${queueParams.name}@${queueParams.ns} using keys: ${keyQueueProperties}, ${keyQueueRateLimitCounter}`);
const multi = client.multi();
multi.hdel(keyQueueProperties, String(EQueueProperty.RATE_LIMIT));
multi.del(keyQueueRateLimitCounter);
multi.exec((err) => {
if (err) {
this.logger.error(`Failed to clear rate limit: ${err.message}`);
return cb(err);
}
this.logger.info(`Successfully cleared rate limit for queue: ${queueParams.name}@${queueParams.ns}`);
cb();
});
}, cb);
}, cb);
}
set(queue, rateLimit, cb) {
const queueName = typeof queue === 'string' ? queue : `${queue.name}@${queue.ns}`;
this.logger.debug(`Setting rate limit for queue: ${queueName}, limit: ${rateLimit.limit}, interval: ${rateLimit.interval}ms`);
withRedisClient(this.redisClient, (client, cb) => {
this.logger.debug(`Validating queue parameters for: ${queueName}`);
_parseQueueParamsAndValidate(client, queue, (err, queueParams) => {
if (err) {
this.logger.error(`Failed to validate queue parameters: ${err.message}`);
return cb(err);
}
if (!queueParams) {
this.logger.error('Queue parameters validation returned empty result');
return cb(new CallbackEmptyReplyError());
}
const limit = Number(rateLimit.limit);
if (isNaN(limit) || limit <= 0) {
this.logger.error(`Invalid rate limit value: ${rateLimit.limit}`);
return cb(new QueueRateLimitInvalidLimitError());
}
const interval = Number(rateLimit.interval);
if (isNaN(interval) || interval < 1000) {
this.logger.error(`Invalid rate limit interval: ${rateLimit.interval}`);
return cb(new QueueRateLimitInvalidIntervalError());
}
const validatedRateLimit = { interval, limit };
this.logger.debug(`Validated rate limit: ${limit} messages per ${interval}ms for queue: ${queueParams.name}@${queueParams.ns}`);
const { keyQueueProperties } = redisKeys.getQueueKeys(queueParams, null);
this.logger.debug(`Setting rate limit using key: ${keyQueueProperties}`);
client.runScript(ELuaScriptName.SET_QUEUE_RATE_LIMIT, [keyQueueProperties], [EQueueProperty.RATE_LIMIT, JSON.stringify(validatedRateLimit)], (err, reply) => {
if (err) {
this.logger.error(`Failed to set rate limit: ${err.message}`);
return cb(err);
}
if (reply !== 'OK') {
this.logger.error(`Queue not found when setting rate limit: ${queueParams.name}@${queueParams.ns}`);
return cb(new QueueRateLimitQueueNotFoundError());
}
this.logger.info(`Successfully set rate limit for queue: ${queueParams.name}@${queueParams.ns}, limit: ${limit}, interval: ${interval}ms`);
cb();
});
});
}, cb);
}
hasExceeded(queue, rateLimit, cb) {
const queueName = typeof queue === 'string' ? queue : `${queue.name}@${queue.ns}`;
this.logger.debug(`Checking if rate limit exceeded for queue: ${queueName}, limit: ${rateLimit.limit}, interval: ${rateLimit.interval}ms`);
withRedisClient(this.redisClient, (client, cb) => {
this.logger.debug(`Validating queue parameters for: ${queueName}`);
_parseQueueParamsAndValidate(client, queue, (err, queueParams) => {
if (err) {
this.logger.error(`Failed to validate queue parameters: ${err.message}`);
return cb(err);
}
if (!queueParams) {
this.logger.error('Queue parameters validation returned empty result');
return cb(new CallbackEmptyReplyError());
}
this.logger.debug(`Checking rate limit for queue: ${queueParams.name}@${queueParams.ns}`);
_hasRateLimitExceeded(client, queueParams, rateLimit, (err, hasExceeded) => {
if (err) {
this.logger.error(`Failed to check rate limit: ${err.message}`);
return cb(err);
}
this.logger.debug(`Rate limit check result for queue ${queueParams.name}@${queueParams.ns}: ${hasExceeded ? 'exceeded' : 'not exceeded'}`);
cb(null, hasExceeded);
});
});
}, cb);
}
get(queue, cb) {
const queueName = typeof queue === 'string' ? queue : `${queue.name}@${queue.ns}`;
this.logger.debug(`Getting rate limit for queue: ${queueName}`);
withRedisClient(this.redisClient, (client, cb) => {
this.logger.debug(`Validating queue parameters for: ${queueName}`);
async.withCallback((cb) => _parseQueueParamsAndValidate(client, queue, cb), (queueParams, cb) => {
const { keyQueueProperties } = redisKeys.getQueueKeys(queueParams, null);
this.logger.debug(`Getting rate limit using key: ${keyQueueProperties}`);
client.hget(keyQueueProperties, String(EQueueProperty.RATE_LIMIT), (err, reply) => {
if (err) {
this.logger.error(`Failed to get rate limit: ${err.message}`);
return cb(err);
}
if (!reply) {
this.logger.debug(`No rate limit found for queue: ${queueParams.name}@${queueParams.ns}`);
return cb(null, null);
}
const rateLimit = JSON.parse(reply);
this.logger.debug(`Retrieved rate limit for queue ${queueParams.name}@${queueParams.ns}: limit: ${rateLimit.limit}, interval: ${rateLimit.interval}ms`);
cb(null, rateLimit);
});
}, cb);
}, cb);
}
shutdown = (cb) => {
this.logger.debug('Shutting down QueueRateLimit');
async.series([this.queue.shutdown, this.redisClient.shutdown], (err) => {
if (err) {
this.logger.error(`Error during shutdown: ${err.message}`);
return cb(err);
}
this.logger.info('QueueRateLimit shutdown completed successfully');
cb();
});
};
}
//# sourceMappingURL=queue-rate-limit.js.map