cleaned up cache lib
This commit is contained in:
parent
ee57b66391
commit
e98b1d8ae2
9 changed files with 637 additions and 1209 deletions
194
libs/cache/src/connection-manager.ts
vendored
Normal file
194
libs/cache/src/connection-manager.ts
vendored
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
import Redis from 'ioredis';
|
||||
import { getLogger } from '@stock-bot/logger';
|
||||
import { dragonflyConfig } from '@stock-bot/config';
|
||||
|
||||
interface ConnectionConfig {
|
||||
name: string;
|
||||
singleton?: boolean;
|
||||
db?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Redis Connection Manager for managing shared and unique connections
|
||||
*/
|
||||
export class RedisConnectionManager {
|
||||
private connections = new Map<string, Redis>();
|
||||
private static sharedConnections = new Map<string, Redis>();
|
||||
private static instance: RedisConnectionManager;
|
||||
private logger = getLogger('redis-connection-manager');
|
||||
|
||||
// Singleton pattern for the manager itself
|
||||
static getInstance(): RedisConnectionManager {
|
||||
if (!this.instance) {
|
||||
this.instance = new RedisConnectionManager();
|
||||
}
|
||||
return this.instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or create a Redis connection
|
||||
* @param config Connection configuration
|
||||
* @returns Redis connection instance
|
||||
*/
|
||||
getConnection(config: ConnectionConfig): Redis {
|
||||
const { name, singleton = false, db } = config;
|
||||
|
||||
if (singleton) {
|
||||
// Use shared connection across all instances
|
||||
if (!RedisConnectionManager.sharedConnections.has(name)) {
|
||||
const connection = this.createConnection(name, db);
|
||||
RedisConnectionManager.sharedConnections.set(name, connection);
|
||||
this.logger.info(`Created shared Redis connection: ${name}`);
|
||||
}
|
||||
return RedisConnectionManager.sharedConnections.get(name)!;
|
||||
} else {
|
||||
// Create unique connection per instance
|
||||
const uniqueName = `${name}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
||||
const connection = this.createConnection(uniqueName, db);
|
||||
this.connections.set(uniqueName, connection);
|
||||
this.logger.info(`Created unique Redis connection: ${uniqueName}`);
|
||||
return connection;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Redis connection with configuration
|
||||
*/
|
||||
private createConnection(name: string, db?: number): Redis {
|
||||
const redisConfig = {
|
||||
host: dragonflyConfig.DRAGONFLY_HOST,
|
||||
port: dragonflyConfig.DRAGONFLY_PORT,
|
||||
password: dragonflyConfig.DRAGONFLY_PASSWORD || undefined,
|
||||
username: dragonflyConfig.DRAGONFLY_USERNAME || undefined,
|
||||
db: db ?? dragonflyConfig.DRAGONFLY_DATABASE,
|
||||
maxRetriesPerRequest: dragonflyConfig.DRAGONFLY_MAX_RETRIES,
|
||||
retryDelayOnFailover: dragonflyConfig.DRAGONFLY_RETRY_DELAY,
|
||||
connectTimeout: dragonflyConfig.DRAGONFLY_CONNECT_TIMEOUT,
|
||||
commandTimeout: dragonflyConfig.DRAGONFLY_COMMAND_TIMEOUT,
|
||||
keepAlive: dragonflyConfig.DRAGONFLY_ENABLE_KEEPALIVE ? dragonflyConfig.DRAGONFLY_KEEPALIVE_INTERVAL * 1000 : 0,
|
||||
connectionName: name,
|
||||
lazyConnect: true,
|
||||
...(dragonflyConfig.DRAGONFLY_TLS && {
|
||||
tls: {
|
||||
cert: dragonflyConfig.DRAGONFLY_TLS_CERT_FILE || undefined,
|
||||
key: dragonflyConfig.DRAGONFLY_TLS_KEY_FILE || undefined,
|
||||
ca: dragonflyConfig.DRAGONFLY_TLS_CA_FILE || undefined,
|
||||
rejectUnauthorized: !dragonflyConfig.DRAGONFLY_TLS_SKIP_VERIFY,
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
const redis = new Redis(redisConfig);
|
||||
|
||||
// Setup event handlers
|
||||
redis.on('connect', () => {
|
||||
this.logger.info(`Redis connection established: ${name}`);
|
||||
});
|
||||
|
||||
redis.on('ready', () => {
|
||||
this.logger.info(`Redis connection ready: ${name}`);
|
||||
});
|
||||
|
||||
redis.on('error', (err) => {
|
||||
this.logger.error(`Redis connection error for ${name}:`, err);
|
||||
});
|
||||
|
||||
redis.on('close', () => {
|
||||
this.logger.info(`Redis connection closed: ${name}`);
|
||||
});
|
||||
|
||||
redis.on('reconnecting', () => {
|
||||
this.logger.info(`Redis reconnecting: ${name}`);
|
||||
});
|
||||
|
||||
return redis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close a specific connection
|
||||
*/
|
||||
async closeConnection(connection: Redis): Promise<void> {
|
||||
try {
|
||||
await connection.quit();
|
||||
} catch (error) {
|
||||
this.logger.error('Error closing Redis connection:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close all connections managed by this instance
|
||||
*/
|
||||
async closeAllConnections(): Promise<void> {
|
||||
// Close instance-specific connections
|
||||
const instancePromises = Array.from(this.connections.values()).map(conn =>
|
||||
this.closeConnection(conn)
|
||||
);
|
||||
await Promise.all(instancePromises);
|
||||
this.connections.clear();
|
||||
|
||||
// Close shared connections (only if this is the last instance)
|
||||
if (RedisConnectionManager.instance === this) {
|
||||
const sharedPromises = Array.from(RedisConnectionManager.sharedConnections.values()).map(conn =>
|
||||
this.closeConnection(conn)
|
||||
);
|
||||
await Promise.all(sharedPromises);
|
||||
RedisConnectionManager.sharedConnections.clear();
|
||||
}
|
||||
|
||||
this.logger.info('All Redis connections closed');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get connection statistics
|
||||
*/
|
||||
getConnectionCount(): { shared: number; unique: number } {
|
||||
return {
|
||||
shared: RedisConnectionManager.sharedConnections.size,
|
||||
unique: this.connections.size
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all connection names for monitoring
|
||||
*/
|
||||
getConnectionNames(): { shared: string[]; unique: string[] } {
|
||||
return {
|
||||
shared: Array.from(RedisConnectionManager.sharedConnections.keys()),
|
||||
unique: Array.from(this.connections.keys())
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Health check for all connections
|
||||
*/
|
||||
async healthCheck(): Promise<{ healthy: boolean; details: Record<string, boolean> }> {
|
||||
const details: Record<string, boolean> = {};
|
||||
let allHealthy = true;
|
||||
|
||||
// Check shared connections
|
||||
for (const [name, connection] of RedisConnectionManager.sharedConnections) {
|
||||
try {
|
||||
await connection.ping();
|
||||
details[`shared:${name}`] = true;
|
||||
} catch (error) {
|
||||
details[`shared:${name}`] = false;
|
||||
allHealthy = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check instance connections
|
||||
for (const [name, connection] of this.connections) {
|
||||
try {
|
||||
await connection.ping();
|
||||
details[`unique:${name}`] = true;
|
||||
} catch (error) {
|
||||
details[`unique:${name}`] = false;
|
||||
allHealthy = false;
|
||||
}
|
||||
}
|
||||
|
||||
return { healthy: allHealthy, details };
|
||||
}
|
||||
}
|
||||
|
||||
export default RedisConnectionManager;
|
||||
Loading…
Add table
Add a link
Reference in a new issue