- 重构config层为配置中心架构,支持动态配置管理 - 统一core层命名规范(event-bus→event, circuit-breaker→breaker, domain-sdk→sdk) - 修复数据库连接配置路径问题 - 实现配置中心完整功能:系统配置、动态配置、配置验证、统计 - 优化目录结构,为微服务架构做准备 - 修复TypeScript编译错误和依赖注入问题
251 lines
7.2 KiB
TypeScript
251 lines
7.2 KiB
TypeScript
import { Injectable, Inject, Logger } from '@nestjs/common';
|
|
import { CACHE_MANAGER } from '@nestjs/cache-manager';
|
|
import type { Cache } from 'cache-manager';
|
|
import { Redis } from 'ioredis';
|
|
import { CacheService } from './cacheService';
|
|
|
|
export interface MultiLevelCacheOptions {
|
|
l1Ttl?: number; // L1 缓存时间(秒)
|
|
l2Ttl?: number; // L2 缓存时间(秒)
|
|
prefix?: string;
|
|
}
|
|
|
|
@Injectable()
|
|
export class MultiLevelCacheService {
|
|
private readonly logger = new Logger(MultiLevelCacheService.name);
|
|
private readonly appPrefix = 'wwjcloud'; // 使用固定前缀,避免硬编码
|
|
|
|
constructor(
|
|
@Inject(CACHE_MANAGER) private l1Cache: Cache, // 内存缓存
|
|
@Inject('REDIS_CLIENT') private l2Cache: Redis, // Redis 缓存
|
|
private cacheService: CacheService,
|
|
) {}
|
|
|
|
/**
|
|
* 获取缓存(多级)
|
|
*/
|
|
async get<T>(key: string, options?: MultiLevelCacheOptions): Promise<T | null> {
|
|
try {
|
|
const fullKey = this.buildKey(key, options?.prefix);
|
|
|
|
// L1: 内存缓存
|
|
const l1Value = await this.l1Cache.get<T>(fullKey);
|
|
if (l1Value !== undefined && l1Value !== null) {
|
|
this.logger.debug(`L1 cache hit: ${fullKey}`);
|
|
return l1Value as T;
|
|
}
|
|
|
|
// L2: Redis 缓存
|
|
const l2Value = await this.l2Cache.get(fullKey);
|
|
if (l2Value !== null && l2Value !== undefined) {
|
|
const parsedValue = JSON.parse(l2Value) as T;
|
|
// 回填到 L1 缓存
|
|
await this.l1Cache.set(fullKey, parsedValue, options?.l1Ttl || 60);
|
|
this.logger.debug(`L2 cache hit: ${fullKey}`);
|
|
return parsedValue;
|
|
}
|
|
|
|
this.logger.debug(`Cache miss: ${fullKey}`);
|
|
return null;
|
|
} catch (error) {
|
|
this.logger.error(`Cache get error: ${error.message}`, error.stack);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 设置缓存(多级)
|
|
*/
|
|
async set<T>(key: string, value: T, options?: MultiLevelCacheOptions): Promise<void> {
|
|
const fullKey = this.buildKey(key, options?.prefix);
|
|
|
|
try {
|
|
// 并行设置 L1 和 L2 缓存
|
|
await Promise.all([
|
|
this.l1Cache.set(fullKey, value, options?.l1Ttl || 60),
|
|
this.l2Cache.setex(fullKey, options?.l2Ttl || 300, JSON.stringify(value)),
|
|
]);
|
|
|
|
this.logger.debug(`Multi-level cache set: ${fullKey}`);
|
|
} catch (error) {
|
|
this.logger.error(`Multi-level cache set error: ${error.message}`, error.stack);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 删除缓存(多级)
|
|
*/
|
|
async del(key: string, options?: MultiLevelCacheOptions): Promise<void> {
|
|
const fullKey = this.buildKey(key, options?.prefix);
|
|
|
|
try {
|
|
// 并行删除 L1 和 L2 缓存
|
|
await Promise.all([
|
|
this.l1Cache.del(fullKey),
|
|
this.l2Cache.del(fullKey),
|
|
]);
|
|
|
|
this.logger.debug(`Multi-level cache del: ${fullKey}`);
|
|
} catch (error) {
|
|
this.logger.error(`Multi-level cache del error: ${error.message}`, error.stack);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 批量删除缓存
|
|
*/
|
|
async delPattern(pattern: string): Promise<void> {
|
|
try {
|
|
// 获取匹配的键
|
|
const keys = await this.l2Cache.keys(pattern);
|
|
|
|
if (keys.length > 0) {
|
|
// 删除 L2 缓存
|
|
await this.l2Cache.del(...keys);
|
|
|
|
// 删除 L1 缓存
|
|
const l1Promises = keys.map(key => this.l1Cache.del(key));
|
|
await Promise.allSettled(l1Promises);
|
|
|
|
this.logger.debug(`Multi-level cache del pattern: ${pattern}, deleted ${keys.length} keys`);
|
|
}
|
|
} catch (error) {
|
|
this.logger.error(`Multi-level cache del pattern error: ${error.message}`, error.stack);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 获取或设置缓存(缓存穿透保护)
|
|
*/
|
|
async getOrSet<T>(
|
|
key: string,
|
|
factory: () => Promise<T>,
|
|
options?: MultiLevelCacheOptions
|
|
): Promise<T> {
|
|
const fullKey = this.buildKey(key, options?.prefix);
|
|
|
|
try {
|
|
// 先尝试获取缓存
|
|
let value = await this.get<T>(fullKey, options);
|
|
|
|
if (value !== null) {
|
|
return value;
|
|
}
|
|
|
|
// 缓存未命中,执行工厂函数
|
|
value = await factory();
|
|
|
|
// 设置缓存
|
|
await this.set(key, value, options);
|
|
|
|
return value;
|
|
} catch (error) {
|
|
this.logger.error(`Multi-level cache getOrSet error: ${error.message}`, error.stack);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 预热缓存
|
|
*/
|
|
async warmup<T>(
|
|
keys: string[],
|
|
factory: (key: string) => Promise<T>,
|
|
options?: MultiLevelCacheOptions
|
|
): Promise<void> {
|
|
this.logger.log(`Starting cache warmup for ${keys.length} keys`);
|
|
|
|
const promises = keys.map(async (key) => {
|
|
try {
|
|
const value = await factory(key);
|
|
await this.set(key, value, options);
|
|
this.logger.debug(`Cache warmed up: ${key}`);
|
|
} catch (error) {
|
|
this.logger.error(`Cache warmup failed for key ${key}: ${error.message}`);
|
|
}
|
|
});
|
|
|
|
await Promise.allSettled(promises);
|
|
this.logger.log('Cache warmup completed');
|
|
}
|
|
|
|
/**
|
|
* 获取缓存统计信息
|
|
*/
|
|
async getStats(): Promise<{
|
|
l1Stats: { size: number; hitRate: number };
|
|
l2Stats: { size: number; hitRate: number };
|
|
totalHitRate: number;
|
|
}> {
|
|
try {
|
|
// 获取 L2 缓存统计
|
|
const l2Info = await this.l2Cache.info('memory');
|
|
const l2Keys = await this.l2Cache.dbsize();
|
|
|
|
// 解析 L2 内存使用
|
|
const memoryMatch = l2Info.match(/used_memory_human:(\S+)/);
|
|
const l2Memory = memoryMatch ? memoryMatch[1] : '0B';
|
|
|
|
return {
|
|
l1Stats: {
|
|
size: 0, // 需要实现 L1 缓存大小统计
|
|
hitRate: 0, // 需要实现命中率统计
|
|
},
|
|
l2Stats: {
|
|
size: this.parseMemoryUsage(l2Memory),
|
|
hitRate: 0, // 需要实现命中率统计
|
|
},
|
|
totalHitRate: 0, // 需要实现总命中率统计
|
|
};
|
|
} catch (error) {
|
|
this.logger.error(`Multi-level cache stats error: ${error.message}`, error.stack);
|
|
return {
|
|
l1Stats: { size: 0, hitRate: 0 },
|
|
l2Stats: { size: 0, hitRate: 0 },
|
|
totalHitRate: 0,
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 清空所有缓存
|
|
*/
|
|
async clear(): Promise<void> {
|
|
try {
|
|
// 直接使用 Redis 的 FLUSHDB 命令清空 L2 缓存
|
|
// L1 缓存会在下次访问时自动失效
|
|
await this.l2Cache.flushdb();
|
|
|
|
this.logger.debug('Multi-level cache cleared');
|
|
} catch (error) {
|
|
this.logger.error(`Multi-level cache clear error: ${error.message}`, error.stack);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 构建缓存键
|
|
*/
|
|
private buildKey(key: string, prefix?: string): string {
|
|
const finalPrefix = prefix ? `${this.appPrefix}:ml:${prefix}` : `${this.appPrefix}:ml`;
|
|
return `${finalPrefix}:${key}`;
|
|
}
|
|
|
|
/**
|
|
* 解析内存使用量
|
|
*/
|
|
private parseMemoryUsage(memoryStr: string): number {
|
|
const match = memoryStr.match(/^(\d+(?:\.\d+)?)([KMGT]?B)$/);
|
|
if (!match) return 0;
|
|
|
|
const [, value, unit] = match;
|
|
const numValue = parseFloat(value);
|
|
|
|
switch (unit) {
|
|
case 'KB': return numValue * 1024;
|
|
case 'MB': return numValue * 1024 * 1024;
|
|
case 'GB': return numValue * 1024 * 1024 * 1024;
|
|
case 'TB': return numValue * 1024 * 1024 * 1024 * 1024;
|
|
default: return numValue;
|
|
}
|
|
}
|
|
}
|