首頁 > web前端 > js教程 > 主體

使用 TypeScript 和 ioredis 在 Node.js 中建立高效能快取管理器

Patricia Arquette
發布: 2024-10-30 09:43:27
原創
889 人瀏覽過

Building a High-Performance Cache Manager in Node.js with TypeScript and ioredis

使用基於 ioredis 建置的多功能、易於使用的快取管理器來提升 Node.js 應用程式的效能。簡化快取、優化效率並簡化操作。

我根據自己的需求開發了一個基於 ioredis 的類,重點關注易用性和性能。它包括 TypeScript 支持,旨在實現簡單使用和高效操作。它仍然可以進一步改進和優化,可以動態資料庫使用和更詳細的錯誤處理。我想與您分享,如果您有任何回饋或改進建議,我將不勝感激。

import Redis, { type RedisOptions } from 'ioredis';

interface CacheConfig {
    defaultTTL?: number;
}

export class cacheManager {
    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,
            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);

            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;
    }

    public static getInstance(config?: CacheConfig): cacheManager {
        if (!cacheManager.instance) {
            cacheManager.instance = new cacheManager(config);
        }
        return cacheManager.instance;
    }

    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');
    }

    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);
            return value ? JSON.parse(value) : null;
        } catch (error) {
            console.error('getValue Error:', error);
            return null;
        }
    }

    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);
            }

            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);
                        } catch {
                            output[key] = null;
                        }
                    } else {
                        output[key] = null;
                    }
                } else {
                    output[key] = null;
                }
            });

            return output;
        } catch (error) {
            console.error('getMultiple Error:', error);
            return {};
        }
    }

    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);

            if (ttl) {
                await cacheManager.redisClient.setex(this.currentKey, ttl, stringValue);
            } else {
                await cacheManager.redisClient.set(this.currentKey, stringValue);
            }

            return true;
        } catch (error) {
            console.error('setValue Error:', error);
            return false;
        }
    }

    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);
                    if (ttl) {
                        pipeline.setex(key, ttl, stringValue);
                    } else {
                        pipeline.set(key, stringValue);
                    }
                }

                await pipeline.exec();
            }
            return true;
        } catch (error) {
            console.error('setBulkValue Error:', error);
            return false;
        }
    }

    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;
        }
    }

    public async delete(): Promise<boolean> {
        try {
            if (!this.currentKey) {
                throw new Error('Key is required');
            }
            await cacheManager.redisClient.del(this.currentKey);
            return true;
        } catch (error) {
            console.error('delete Error:', error);
            return false;
        }
    }

    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;
        }
    }

    public async getTTL(): Promise<number> {
        try {
            if (!this.currentKey) {
                throw new Error('Key is required');
            }
            return await cacheManager.redisClient.ttl(this.currentKey);
        } catch (error) {
            console.error('getTTL Error:', error);
            return -1;
        }
    }

    public static async disconnect(): Promise<void> {
        if (cacheManager.redisClient) {
            await cacheManager.redisClient.quit();
        }
    }
}

登入後複製

以上是使用 TypeScript 和 ioredis 在 Node.js 中建立高效能快取管理器的詳細內容。更多資訊請關注PHP中文網其他相關文章!

來源:dev.to
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
作者最新文章
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板
關於我們 免責聲明 Sitemap
PHP中文網:公益線上PHP培訓,幫助PHP學習者快速成長!