使用 TypeScript 和 ioredis 在 Node.js 中构建高性能缓存管理器
阅读:29
点赞:0
为了提升 Node.js 应用程序的性能,本文介绍了一款基于 ioredis 的缓存管理器类。该类注重易用性和性能优化,支持 TypeScript,并致力于简化缓存操作,优化效率,简化操作流程。
一. 引言
缓存在现代 Web 应用程序中扮演着至关重要的角色。它不仅可以减少数据库负载,还能显著提高应用响应速度。为此,我们开发了一个基于 ioredis 的缓存管理类,该类旨在提供简单易用的 API,同时具备良好的性能表现。以下是该缓存管理类的基本介绍和使用示例。
二. 缓存管理类的设计与实现
1. 类的导入与定义
import Redis, { type RedisOptions } from 'ioredis';
interface CacheConfig {
defaultTTL?: number;
}
export class CacheManager {
2. 类的构造函数
构造函数用于初始化缓存管理器实例,并设定默认的 TTL 时间(Time To Live)。
private static instance: CacheManager;
private static redisClient: Redis;
private currentKey: string | null;
private defaultTTL: number;
private static readonly DEFAULT_TTL = 3600;
private constructor(config?: CacheConfig) {
const redisConfig: RedisOptions = {
db: 2, // 选择数据库 2
retryStrategy: (times: number) => {
const delay = Math.min(times * 50, 2000); // 设置重试策略
return delay;
},
lazyConnect: true, // 延迟连接
maxRetriesPerRequest: 3, // 每次请求的最大重试次数
enableReadyCheck: true, // 启用准备检查
autoResubscribe: true, // 自动重新订阅
autoResendUnfulfilledCommands: true, // 自动重发未完成命令
reconnectOnError: (err: Error) => {
const targetError = 'READONLY'; // 当遇到只读错误时尝试重新连接
return err.message.includes(targetError);
},
};
if (!CacheManager.redisClient) {
CacheManager.redisClient = new Redis(redisConfig); // 创建 Redis 实例
CacheManager.redisClient.on('error', (error: Error) => {
console.error('Redis Client Error:', error); // 错误监听
});
CacheManager.redisClient.on('connect', () => {
console.debug('Redis Client Connected'); // 连接成功监听
});
CacheManager.redisClient.on('ready', () => {
console.debug('Redis Client Ready'); // 准备就绪监听
});
}
this.currentKey = null; // 当前键值
this.defaultTTL = config?.defaultTTL ?? CacheManager.DEFAULT_TTL; // 默认 TTL
}
3. 单例模式的实现
通过单例模式确保在整个应用程序中只存在一个缓存管理器实例。
public static getInstance(config?: CacheConfig): CacheManager {
if (!CacheManager.instance) {
CacheManager.instance = new CacheManager(config);
}
return CacheManager.instance;
}
4. 设置键值
设置当前操作的键值,并进行有效性验证。
public key(key: string): CacheManager {
this.validateKey(key);
this.currentKey = key;
return this;
}
private validateKey(key: string): void {
if (key.length > 100) throw new Error('Key too long'); // 验证键长度
if (!/^[\w:-]+$/.test(key)) throw new Error('Invalid key format'); // 验证键格式
}
5. 获取缓存值
获取当前键对应的缓存值。
public async getValue<T>(): Promise<T | null> {
try {
if (!this.currentKey) {
throw new Error('Key is required'); // 验证键是否已设置
}
const value = await CacheManager.redisClient.get(this.currentKey); // 从 Redis 获取值
return value ? JSON.parse(value) : null; // 解析 JSON 字符串
} catch (error) {
console.error('getValue Error:', error); // 错误处理
return null;
}
}
6. 批量获取缓存值
批量获取多个键对应的缓存值。
public async getMultiple<T>(keys: string[]): Promise<Record<string, T | null>> {
try {
const pipeline = CacheManager.redisClient.pipeline(); // 创建管道
for (const key of keys) {
pipeline.get(key); // 向管道中添加 GET 命令
}
type PipelineResult = [Error | null, string | null][] | null;
const results = (await pipeline.exec()) as PipelineResult; // 执行管道
const output: Record<string, T | null> = {}; // 创建结果对象
if (!results) {
return output;
}
keys.forEach((key, index) => {
const result = results[index];
if (result) {
const [err, value] = result;
if (!err && value) {
try {
output[key] = JSON.parse(value); // 解析 JSON 字符串
} catch {
output[key] = null; // 处理解析错误
}
} else {
output[key] = null; // 处理未找到情况
}
} else {
output[key] = null; // 处理未找到情况
}
});
return output;
} catch (error) {
console.error('getMultiple Error:', error); // 错误处理
return {};
}
}
7. 设置缓存值
设置当前键对应的缓存值,并可选设置过期时间。
public async setValue<T>(value: T, ttl: number = this.defaultTTL): Promise<boolean> {
try {
if (!this.currentKey) {
throw new Error('Key is required'); // 验证键是否已设置
}
const stringValue = JSON.stringify(value); // 将值转换为 JSON 字符串
if (ttl) {
await CacheManager.redisClient.setex(this.currentKey, ttl, stringValue); // 设置值并设置过期时间
} else {
await CacheManager.redisClient.set(this.currentKey, stringValue); // 设置值
}
return true; // 操作成功返回 true
} catch (error) {
console.error('setValue Error:', error); // 错误处理
return false; // 操作失败返回 false
}
}
8. 批量设置缓存值
批量设置多个键对应的缓存值,并可选设置过期时间。
public async setBulkValue<T>(keyValuePairs: Record<string, T>, ttl: number = this.defaultTTL, batchSize = 1000): Promise<boolean> {
try {
const entries = Object.entries(keyValuePairs); // 获取键值对数组
for (let i = 0; i < entries.length; i += batchSize) {
const batch = entries.slice(i, i + batchSize); // 分割数组
const pipeline = CacheManager.redisClient.pipeline(); // 创建管道
for (const [key, value] of batch) {
const stringValue = JSON.stringify(value); // 将值转换为 JSON 字符串
if (ttl) {
pipeline.setex(key, ttl, stringValue); // 设置值并设置过期时间
} else {
pipeline.set(key, stringValue); // 设置值
}
}
await pipeline.exec(); // 执行管道
}
return true; // 操作成功返回 true
} catch (error) {
console.error('setBulkValue Error:', error); // 错误处理
return false; // 操作失败返回 false
}
}
9. 获取或设置缓存值
如果键不存在,则调用回调函数获取值并设置到缓存中。
public async getOrSetValue<T>(fallbackFn: () => Promise<T>, ttl: number = this.defaultTTL): Promise<T | null> {
try {
if (!this.currentKey) {
throw new Error('Key is required'); // 验证键是否已设置
}
const cachedValue = await this.getValue<T>(); // 尝试从缓存获取值
if (cachedValue !== null) {
return cachedValue; // 如果缓存中有值则直接返回
}
const value = await fallbackFn(); // 如果缓存中没有值则调用回调函数获取值
await this.setValue(value, ttl); // 将值设置到缓存中
return value; // 返回值
} catch (error) {
console.error('getOrSetValue Error:', error); // 错误处理
return null; // 操作失败返回 null
}
}
10. 删除缓存值
删除当前键对应的缓存值。
public async delete(): Promise<boolean> {
try {
if (!this.currentKey) {
throw new Error('Key is required'); // 验证键是否已设置
}
await CacheManager.redisClient.del(this.currentKey); // 删除键
return true; // 操作成功返回 true
} catch (error) {
console.error('delete Error:', error); // 错误处理
return false; // 操作失败返回 false
}
}
11. 检查键是否存在
检查当前键是否存在。
public async exists(): Promise<boolean> {
try {
if (!this.currentKey) {
throw new Error('Key is required'); // 验证键是否已设置
}
return (await CacheManager.redisClient.exists(this.currentKey)) === 1; // 检查键是否存在
} catch (error) {
console.error('exists Error:', error); // 错误处理
return false; // 操作失败返回 false
}
}
12. 获取键的剩余过期时间
获取当前键的剩余过期时间。
public async getTTL(): Promise<number> {
try {
if (!this.currentKey) {
throw new Error('Key is required'); // 验证键是否已设置
}
return await CacheManager.redisClient.ttl(this.currentKey); // 获取 TTL
} catch (error) {
console.error('getTTL Error:', error); // 错误处理
return -1; // 操作失败返回 -1
}
}
13. 断开连接
断开与 Redis 的连接。
public static async disconnect(): Promise<void> {
if (CacheManager.redisClient) {
await CacheManager.redisClient.quit(); // 断开连接
}
}
三. 总结
-
通过上述缓存管理类的设计,我们实现了对 Redis 缓存的高效管理和操作,提升了 Node.js 应用的性能。 -
类支持单例模式、批量操作等功能,并且通过 TypeScript 提供了类型安全。 -
未来可以考虑添加更多高级特性,如动态数据库选择、更详细的错误处理机制等,进一步增强其功能性和稳定性。
通过使用此类缓存管理器,开发者可以更容易地集成缓存机制到他们的应用中,从而改善用户体验,减轻数据库压力。